chore: rename project from glossa to lila
- Update all package names from @glossa/* to @lila/* - Update all imports, container names, volume names - Update documentation references - Recreate database with new credentials
This commit is contained in:
parent
1699f78f0b
commit
3f7bc4111e
37 changed files with 116 additions and 182 deletions
|
|
@ -1 +1 @@
|
||||||
# glossa
|
# lila
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"name": "@glossa/api",
|
"name": "@lila/api",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|
@ -10,8 +10,8 @@
|
||||||
"test": "vitest"
|
"test": "vitest"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@glossa/db": "workspace:*",
|
"@lila/db": "workspace:*",
|
||||||
"@glossa/shared": "workspace:*",
|
"@lila/shared": "workspace:*",
|
||||||
"better-auth": "^1.6.2",
|
"better-auth": "^1.6.2",
|
||||||
"cors": "^2.8.6",
|
"cors": "^2.8.6",
|
||||||
"express": "^5.2.1"
|
"express": "^5.2.1"
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,9 @@
|
||||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||||
import request from "supertest";
|
import request from "supertest";
|
||||||
|
|
||||||
vi.mock("@glossa/db", () => ({
|
vi.mock("@lila/db", () => ({ getGameTerms: vi.fn(), getDistractors: vi.fn() }));
|
||||||
getGameTerms: vi.fn(),
|
|
||||||
getDistractors: vi.fn(),
|
|
||||||
}));
|
|
||||||
|
|
||||||
import { getGameTerms, getDistractors } from "@glossa/db";
|
import { getGameTerms, getDistractors } from "@lila/db";
|
||||||
import { createApp } from "../app.js";
|
import { createApp } from "../app.js";
|
||||||
|
|
||||||
const app = createApp();
|
const app = createApp();
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import type { Request, Response, NextFunction } from "express";
|
import type { Request, Response, NextFunction } from "express";
|
||||||
import { GameRequestSchema, AnswerSubmissionSchema } from "@glossa/shared";
|
import { GameRequestSchema, AnswerSubmissionSchema } from "@lila/shared";
|
||||||
import { createGameSession, evaluateAnswer } from "../services/gameService.js";
|
import { createGameSession, evaluateAnswer } from "../services/gameService.js";
|
||||||
import { ValidationError } from "../errors/AppError.js";
|
import { ValidationError } from "../errors/AppError.js";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { betterAuth } from "better-auth";
|
import { betterAuth } from "better-auth";
|
||||||
import { drizzleAdapter } from "better-auth/adapters/drizzle";
|
import { drizzleAdapter } from "better-auth/adapters/drizzle";
|
||||||
import { db } from "@glossa/db";
|
import { db } from "@lila/db";
|
||||||
import * as schema from "@glossa/db/schema";
|
import * as schema from "@lila/db/schema";
|
||||||
|
|
||||||
export const auth = betterAuth({
|
export const auth = betterAuth({
|
||||||
database: drizzleAdapter(db, { provider: "pg", schema }),
|
database: drizzleAdapter(db, { provider: "pg", schema }),
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,9 @@
|
||||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||||
import type { GameRequest, AnswerSubmission } from "@glossa/shared";
|
import type { GameRequest, AnswerSubmission } from "@lila/shared";
|
||||||
|
|
||||||
vi.mock("@glossa/db", () => ({
|
vi.mock("@lila/db", () => ({ getGameTerms: vi.fn(), getDistractors: vi.fn() }));
|
||||||
getGameTerms: vi.fn(),
|
|
||||||
getDistractors: vi.fn(),
|
|
||||||
}));
|
|
||||||
|
|
||||||
import { getGameTerms, getDistractors } from "@glossa/db";
|
import { getGameTerms, getDistractors } from "@lila/db";
|
||||||
import { createGameSession, evaluateAnswer } from "./gameService.js";
|
import { createGameSession, evaluateAnswer } from "./gameService.js";
|
||||||
|
|
||||||
const mockGetGameTerms = vi.mocked(getGameTerms);
|
const mockGetGameTerms = vi.mocked(getGameTerms);
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { randomUUID } from "crypto";
|
import { randomUUID } from "crypto";
|
||||||
import { getGameTerms, getDistractors } from "@glossa/db";
|
import { getGameTerms, getDistractors } from "@lila/db";
|
||||||
import type {
|
import type {
|
||||||
GameRequest,
|
GameRequest,
|
||||||
GameSession,
|
GameSession,
|
||||||
|
|
@ -7,7 +7,7 @@ import type {
|
||||||
AnswerOption,
|
AnswerOption,
|
||||||
AnswerSubmission,
|
AnswerSubmission,
|
||||||
AnswerResult,
|
AnswerResult,
|
||||||
} from "@glossa/shared";
|
} from "@lila/shared";
|
||||||
import { InMemoryGameSessionStore } from "../gameSessionStore/index.js";
|
import { InMemoryGameSessionStore } from "../gameSessionStore/index.js";
|
||||||
import { NotFoundError } from "../errors/AppError.js";
|
import { NotFoundError } from "../errors/AppError.js";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>glossa</title>
|
<title>lila</title>
|
||||||
<!--TODO: add favicon-->
|
<!--TODO: add favicon-->
|
||||||
<link rel="icon" href="data:," />
|
<link rel="icon" href="data:," />
|
||||||
</head>
|
</head>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"name": "@glossa/web",
|
"name": "@lila/web",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@glossa/shared": "workspace:*",
|
"@lila/shared": "workspace:*",
|
||||||
"@tailwindcss/vite": "^4.2.2",
|
"@tailwindcss/vite": "^4.2.2",
|
||||||
"@tanstack/react-router": "^1.168.1",
|
"@tanstack/react-router": "^1.168.1",
|
||||||
"@tanstack/react-router-devtools": "^1.166.10",
|
"@tanstack/react-router-devtools": "^1.166.10",
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,8 @@ import {
|
||||||
SUPPORTED_POS,
|
SUPPORTED_POS,
|
||||||
DIFFICULTY_LEVELS,
|
DIFFICULTY_LEVELS,
|
||||||
GAME_ROUNDS,
|
GAME_ROUNDS,
|
||||||
} from "@glossa/shared";
|
} from "@lila/shared";
|
||||||
import type { GameRequest } from "@glossa/shared";
|
import type { GameRequest } from "@lila/shared";
|
||||||
|
|
||||||
const LABELS: Record<string, string> = {
|
const LABELS: Record<string, string> = {
|
||||||
en: "English",
|
en: "English",
|
||||||
|
|
@ -92,7 +92,7 @@ export const GameSetup = ({ onStart }: GameSetupProps) => {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col items-center gap-6 w-full max-w-md mx-auto">
|
<div className="flex flex-col items-center gap-6 w-full max-w-md mx-auto">
|
||||||
<div className="bg-white rounded-3xl shadow-lg p-8 w-full text-center">
|
<div className="bg-white rounded-3xl shadow-lg p-8 w-full text-center">
|
||||||
<h1 className="text-3xl font-bold text-purple-900 mb-1">Glossa</h1>
|
<h1 className="text-3xl font-bold text-purple-900 mb-1">lila</h1>
|
||||||
<p className="text-sm text-gray-400">Set up your quiz</p>
|
<p className="text-sm text-gray-400">Set up your quiz</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import type { GameQuestion, AnswerResult } from "@glossa/shared";
|
import type { GameQuestion, AnswerResult } from "@lila/shared";
|
||||||
import { OptionButton } from "./OptionButton";
|
import { OptionButton } from "./OptionButton";
|
||||||
|
|
||||||
type QuestionCardProps = {
|
type QuestionCardProps = {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import type { AnswerResult } from "@glossa/shared";
|
import type { AnswerResult } from "@lila/shared";
|
||||||
|
|
||||||
type ScoreScreenProps = { results: AnswerResult[]; onPlayAgain: () => void };
|
type ScoreScreenProps = { results: AnswerResult[]; onPlayAgain: () => void };
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ const LoginPage = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col items-center justify-center gap-4 p-8">
|
<div className="flex flex-col items-center justify-center gap-4 p-8">
|
||||||
<h1 className="text-2xl font-bold">Sign in to Glossa</h1>
|
<h1 className="text-2xl font-bold">sign in to lila</h1>
|
||||||
<button
|
<button
|
||||||
className="w-64 rounded bg-gray-800 px-4 py-2 text-white hover:bg-gray-700"
|
className="w-64 rounded bg-gray-800 px-4 py-2 text-white hover:bg-gray-700"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { createFileRoute, redirect } from "@tanstack/react-router";
|
import { createFileRoute, redirect } from "@tanstack/react-router";
|
||||||
import { useState, useCallback } from "react";
|
import { useState, useCallback } from "react";
|
||||||
import type { GameSession, GameRequest, AnswerResult } from "@glossa/shared";
|
import type { GameSession, GameRequest, AnswerResult } from "@lila/shared";
|
||||||
import { QuestionCard } from "../components/game/QuestionCard";
|
import { QuestionCard } from "../components/game/QuestionCard";
|
||||||
import { ScoreScreen } from "../components/game/ScoreScreen";
|
import { ScoreScreen } from "../components/game/ScoreScreen";
|
||||||
import { GameSetup } from "../components/game/GameSetup";
|
import { GameSetup } from "../components/game/GameSetup";
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"target": "ES2023",
|
"target": "ES2023",
|
||||||
"types": ["vite/client", "vitest/globals"],
|
"types": ["vite/client", "vitest/globals"],
|
||||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo"
|
||||||
},
|
},
|
||||||
"include": ["src", "vitest.config.ts"],
|
"include": ["src", "vitest.config.ts"]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
services:
|
services:
|
||||||
database:
|
database:
|
||||||
container_name: glossa-database
|
container_name: lila-database
|
||||||
image: postgres:18.3-alpine3.23
|
image: postgres:18.3-alpine3.23
|
||||||
env_file:
|
env_file:
|
||||||
- .env
|
- .env
|
||||||
|
|
@ -9,7 +9,7 @@ services:
|
||||||
ports:
|
ports:
|
||||||
- "5432:5432"
|
- "5432:5432"
|
||||||
volumes:
|
volumes:
|
||||||
- glossa-db:/var/lib/postgresql/data
|
- lila-db:/var/lib/postgresql/data
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
|
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
|
||||||
|
|
@ -18,7 +18,7 @@ services:
|
||||||
retries: 5
|
retries: 5
|
||||||
|
|
||||||
valkey:
|
valkey:
|
||||||
container_name: glossa-valkey
|
container_name: lila-valkey
|
||||||
image: valkey/valkey:9.1-alpine3.23
|
image: valkey/valkey:9.1-alpine3.23
|
||||||
ports:
|
ports:
|
||||||
- "6379:6379"
|
- "6379:6379"
|
||||||
|
|
@ -30,7 +30,7 @@ services:
|
||||||
retries: 5
|
retries: 5
|
||||||
|
|
||||||
api:
|
api:
|
||||||
container_name: glossa-api
|
container_name: lila-api
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: ./apps/api/Dockerfile
|
dockerfile: ./apps/api/Dockerfile
|
||||||
|
|
@ -57,7 +57,7 @@ services:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
|
|
||||||
web:
|
web:
|
||||||
container_name: glossa-web
|
container_name: lila-web
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: ./apps/web/Dockerfile
|
dockerfile: ./apps/web/Dockerfile
|
||||||
|
|
@ -75,4 +75,4 @@ services:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
glossa-db:
|
lila-db:
|
||||||
|
|
|
||||||
|
|
@ -140,7 +140,7 @@ Returns all validation failures at once, not just the first. Output is verbose (
|
||||||
|
|
||||||
### Mocked DB for unit tests (not test database)
|
### Mocked DB for unit tests (not test database)
|
||||||
|
|
||||||
Unit tests mock `@glossa/db` via `vi.mock` — the real database is never touched. Tests run in milliseconds with no infrastructure dependency. Integration tests with a real test DB are deferred post-MVP.
|
Unit tests mock `@lila/db` via `vi.mock` — the real database is never touched. Tests run in milliseconds with no infrastructure dependency. Integration tests with a real test DB are deferred post-MVP.
|
||||||
|
|
||||||
### Co-located test files
|
### Co-located test files
|
||||||
|
|
||||||
|
|
@ -322,7 +322,7 @@ The `exports` field must be an object, not an array:
|
||||||
|
|
||||||
## Known Issues / Dev Notes
|
## Known Issues / Dev Notes
|
||||||
|
|
||||||
### glossa-web has no healthcheck
|
### lila-web has no healthcheck
|
||||||
|
|
||||||
Vite's dev server has no built-in health endpoint. `depends_on` uses API healthcheck as proxy. For production (Nginx), add a health endpoint or TCP port check.
|
Vite's dev server has no built-in health endpoint. `depends_on` uses API healthcheck as proxy. For production (Nginx), add a health endpoint or TCP port check.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,8 +19,8 @@ Progress: resolved 577, reused 0, downloaded 0, added 0, done
|
||||||
WARN Issues with peer dependencies found
|
WARN Issues with peer dependencies found
|
||||||
.
|
.
|
||||||
└─┬ eslint-plugin-react-hooks 7.0.1
|
└─┬ eslint-plugin-react-hooks 7.0.1
|
||||||
└── ✕ unmet peer eslint@"^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0": found 10.0.3
|
└── ✕ unmet peer eslint@"^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0": found 10.0.3
|
||||||
. | +3 +
|
. | +3 +
|
||||||
Done in 5.6s using pnpm v10.33.0
|
Done in 5.6s using pnpm v10.33.0
|
||||||
|
|
||||||
### env managing
|
### env managing
|
||||||
|
|
@ -43,7 +43,7 @@ app publication/verification:
|
||||||
|
|
||||||
Branding and Data Access (Scope) Verification
|
Branding and Data Access (Scope) Verification
|
||||||
|
|
||||||
In addition to brand verification, your app may also need to be verified to use certain scopes. You can view and track this on the Verification Center page:
|
In addition to brand verification, your app may also need to be verified to use certain scopes. You can view and track this on the Verification Center page:
|
||||||
|
|
||||||
Branding status: This tracks the verification of your app's public-facing brand (name, logo, etc.).
|
Branding status: This tracks the verification of your app's public-facing brand (name, logo, etc.).
|
||||||
Data access status: This tracks the verification of the specific data (scopes) your app is requesting to access.
|
Data access status: This tracks the verification of the specific data (scopes) your app is requesting to access.
|
||||||
|
|
@ -51,9 +51,9 @@ In addition to brand verification, your app may also need to be verified to use
|
||||||
Note: You must have a published branding status before you can request verification for data access (scopes).
|
Note: You must have a published branding status before you can request verification for data access (scopes).
|
||||||
Manage App Audience Configuration
|
Manage App Audience Configuration
|
||||||
Publishing Status
|
Publishing Status
|
||||||
Manage your app publishing status in the Audience page of the Google Auth Platform.
|
Manage your app publishing status in the Audience page of the Google Auth Platform.
|
||||||
User Type
|
User Type
|
||||||
Manage your app audience in the Audience page of the Google Auth Platform.
|
Manage your app audience in the Audience page of the Google Auth Platform.
|
||||||
|
|
||||||
[link](https://support.google.com/cloud/answer/15549049?visit_id=01775982668127-2568683599515917262&rd=1#publishing-status&zippy=%2Cpublishing-status%2Cuser-type)
|
[link](https://support.google.com/cloud/answer/15549049?visit_id=01775982668127-2568683599515917262&rd=1#publishing-status&zippy=%2Cpublishing-status%2Cuser-type)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
# Glossa — Roadmap
|
# lila — Roadmap
|
||||||
|
|
||||||
Each phase produces a working increment. Nothing is built speculatively.
|
Each phase produces a working increment. Nothing is built speculatively.
|
||||||
|
|
||||||
|
|
@ -28,6 +28,7 @@ Each phase produces a working increment. Nothing is built speculatively.
|
||||||
**Done when:** API returns quiz sessions with distractors, error handling and tests in place.
|
**Done when:** API returns quiz sessions with distractors, error handling and tests in place.
|
||||||
|
|
||||||
### Data pipeline
|
### Data pipeline
|
||||||
|
|
||||||
- [x] Run `extract-en-it-nouns.py` locally → generates JSON
|
- [x] Run `extract-en-it-nouns.py` locally → generates JSON
|
||||||
- [x] Write Drizzle schema: `terms`, `translations`, `term_glosses`, `decks`, `deck_terms`
|
- [x] Write Drizzle schema: `terms`, `translations`, `term_glosses`, `decks`, `deck_terms`
|
||||||
- [x] Write and run migration (includes CHECK constraints)
|
- [x] Write and run migration (includes CHECK constraints)
|
||||||
|
|
@ -37,11 +38,13 @@ Each phase produces a working increment. Nothing is built speculatively.
|
||||||
- [x] Expand data pipeline — import all OMW languages and POS
|
- [x] Expand data pipeline — import all OMW languages and POS
|
||||||
|
|
||||||
### Schemas
|
### Schemas
|
||||||
|
|
||||||
- [x] Define `GameRequestSchema` in `packages/shared`
|
- [x] Define `GameRequestSchema` in `packages/shared`
|
||||||
- [x] Define `AnswerOption`, `GameQuestion`, `GameSession`, `AnswerSubmission`, `AnswerResult` schemas
|
- [x] Define `AnswerOption`, `GameQuestion`, `GameSession`, `AnswerSubmission`, `AnswerResult` schemas
|
||||||
- [x] Derived types exported from constants (`SupportedLanguageCode`, `SupportedPos`, `DifficultyLevel`)
|
- [x] Derived types exported from constants (`SupportedLanguageCode`, `SupportedPos`, `DifficultyLevel`)
|
||||||
|
|
||||||
### Model layer
|
### Model layer
|
||||||
|
|
||||||
- [x] `getGameTerms()` with POS / language / difficulty / limit filters
|
- [x] `getGameTerms()` with POS / language / difficulty / limit filters
|
||||||
- [x] Double join on `translations` (source + target language)
|
- [x] Double join on `translations` (source + target language)
|
||||||
- [x] Gloss left join
|
- [x] Gloss left join
|
||||||
|
|
@ -49,21 +52,25 @@ Each phase produces a working increment. Nothing is built speculatively.
|
||||||
- [x] Models correctly placed in `packages/db`
|
- [x] Models correctly placed in `packages/db`
|
||||||
|
|
||||||
### Service layer
|
### Service layer
|
||||||
|
|
||||||
- [x] `createGameSession()` — fetches terms, fetches distractors, shuffles options, stores session
|
- [x] `createGameSession()` — fetches terms, fetches distractors, shuffles options, stores session
|
||||||
- [x] `evaluateAnswer()` — looks up session, compares submitted optionId to stored correct answer
|
- [x] `evaluateAnswer()` — looks up session, compares submitted optionId to stored correct answer
|
||||||
- [x] `GameSessionStore` interface + `InMemoryGameSessionStore` (swappable to Valkey)
|
- [x] `GameSessionStore` interface + `InMemoryGameSessionStore` (swappable to Valkey)
|
||||||
|
|
||||||
### API endpoints
|
### API endpoints
|
||||||
|
|
||||||
- [x] `POST /api/v1/game/start` — route, controller, service
|
- [x] `POST /api/v1/game/start` — route, controller, service
|
||||||
- [x] `POST /api/v1/game/answer` — route, controller, service
|
- [x] `POST /api/v1/game/answer` — route, controller, service
|
||||||
- [x] End-to-end pipeline verified with test script
|
- [x] End-to-end pipeline verified with test script
|
||||||
|
|
||||||
### Error handling
|
### Error handling
|
||||||
|
|
||||||
- [x] Typed error classes: `AppError`, `ValidationError` (400), `NotFoundError` (404)
|
- [x] Typed error classes: `AppError`, `ValidationError` (400), `NotFoundError` (404)
|
||||||
- [x] Central error middleware in `app.ts`
|
- [x] Central error middleware in `app.ts`
|
||||||
- [x] Controllers cleaned up: validate → call service → `next(error)` on failure
|
- [x] Controllers cleaned up: validate → call service → `next(error)` on failure
|
||||||
|
|
||||||
### Tests
|
### Tests
|
||||||
|
|
||||||
- [x] Unit tests for `createGameSession` (question shape, options, distractors, gloss)
|
- [x] Unit tests for `createGameSession` (question shape, options, distractors, gloss)
|
||||||
- [x] Unit tests for `evaluateAnswer` (correct, incorrect, missing session, missing question)
|
- [x] Unit tests for `evaluateAnswer` (correct, incorrect, missing session, missing question)
|
||||||
- [x] Integration tests for both endpoints via supertest (200, 400, 404)
|
- [x] Integration tests for both endpoints via supertest (200, 400, 404)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
# Glossa — Project Specification
|
# lila — Project Specification
|
||||||
|
|
||||||
> **This document is the single source of truth for the project.**
|
> **This document is the single source of truth for the project.**
|
||||||
> It is written to be handed to any LLM as context. It contains the project vision, the current MVP scope, the tech stack, the architecture, and the roadmap.
|
> It is written to be handed to any LLM as context. It contains the project vision, the current MVP scope, the tech stack, the architecture, and the roadmap.
|
||||||
|
|
@ -51,7 +51,7 @@ This is the full vision. The MVP deliberately ignores most of it.
|
||||||
|
|
||||||
| Feature | Why cut |
|
| Feature | Why cut |
|
||||||
| ------------------------------- | -------------------------------------- |
|
| ------------------------------- | -------------------------------------- |
|
||||||
| Authentication (Better Auth) | No user accounts needed for a demo |
|
| Authentication (Better Auth) | No user accounts needed for a demo |
|
||||||
| Multiplayer (WebSockets, rooms) | Core quiz works without it |
|
| Multiplayer (WebSockets, rooms) | Core quiz works without it |
|
||||||
| Valkey / Redis cache | Only needed for multiplayer room state |
|
| Valkey / Redis cache | Only needed for multiplayer room state |
|
||||||
| Deployment to Hetzner | Ship to people locally first |
|
| Deployment to Hetzner | Ship to people locally first |
|
||||||
|
|
@ -259,8 +259,8 @@ After completing a task: share the code, ask what to refactor and why. The LLM s
|
||||||
## 11. Post-MVP Ladder
|
## 11. Post-MVP Ladder
|
||||||
|
|
||||||
| Phase | What it adds |
|
| Phase | What it adds |
|
||||||
| ----------------- | -------------------------------------------------------------- |
|
| ----------------- | -------------------------------------------------------------- | ----------------------------------------------------------------------- |
|
||||||
| Auth | Auth | Better Auth (Google + GitHub), embedded in Express API, user rows in DB |
|
| Auth | Auth | Better Auth (Google + GitHub), embedded in Express API, user rows in DB |
|
||||||
| User Stats | Games played, score history, profile page |
|
| User Stats | Games played, score history, profile page |
|
||||||
| Multiplayer Lobby | Room creation, join by code, WebSocket connection |
|
| Multiplayer Lobby | Room creation, join by code, WebSocket connection |
|
||||||
| Multiplayer Game | Simultaneous answers, server timer, live scores, winner screen |
|
| Multiplayer Game | Simultaneous answers, server timer, live scores, winner screen |
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
{
|
{
|
||||||
"name": "glossa",
|
"name": "lila",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "a vocabulary trainer",
|
"description": "a vocabulary trainer",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "pnpm --filter @glossa/shared build && pnpm --filter @glossa/db build && pnpm --filter @glossa/api build",
|
"build": "pnpm --filter @lila/shared build && pnpm --filter @lila/db build && pnpm --filter @lila/api build",
|
||||||
"dev": "concurrently --names \"api,web\" -c \"magenta.bold,green.bold\" \"pnpm --filter @glossa/api dev\" \"pnpm --filter @glossa/web dev\"",
|
"dev": "concurrently --names \"api,web\" -c \"magenta.bold,green.bold\" \"pnpm --filter @lila/api dev\" \"pnpm --filter @lila/web dev\"",
|
||||||
"test": "vitest",
|
"test": "vitest",
|
||||||
"test:run": "vitest run",
|
"test:run": "vitest run",
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
|
|
|
||||||
|
|
@ -110,12 +110,8 @@
|
||||||
"name": "account_user_id_user_id_fk",
|
"name": "account_user_id_user_id_fk",
|
||||||
"tableFrom": "account",
|
"tableFrom": "account",
|
||||||
"tableTo": "user",
|
"tableTo": "user",
|
||||||
"columnsFrom": [
|
"columnsFrom": ["user_id"],
|
||||||
"user_id"
|
"columnsTo": ["id"],
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "cascade",
|
"onDelete": "cascade",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
}
|
}
|
||||||
|
|
@ -149,12 +145,8 @@
|
||||||
"name": "deck_terms_deck_id_decks_id_fk",
|
"name": "deck_terms_deck_id_decks_id_fk",
|
||||||
"tableFrom": "deck_terms",
|
"tableFrom": "deck_terms",
|
||||||
"tableTo": "decks",
|
"tableTo": "decks",
|
||||||
"columnsFrom": [
|
"columnsFrom": ["deck_id"],
|
||||||
"deck_id"
|
"columnsTo": ["id"],
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "cascade",
|
"onDelete": "cascade",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
},
|
},
|
||||||
|
|
@ -162,12 +154,8 @@
|
||||||
"name": "deck_terms_term_id_terms_id_fk",
|
"name": "deck_terms_term_id_terms_id_fk",
|
||||||
"tableFrom": "deck_terms",
|
"tableFrom": "deck_terms",
|
||||||
"tableTo": "terms",
|
"tableTo": "terms",
|
||||||
"columnsFrom": [
|
"columnsFrom": ["term_id"],
|
||||||
"term_id"
|
"columnsTo": ["id"],
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "cascade",
|
"onDelete": "cascade",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
}
|
}
|
||||||
|
|
@ -175,10 +163,7 @@
|
||||||
"compositePrimaryKeys": {
|
"compositePrimaryKeys": {
|
||||||
"deck_terms_deck_id_term_id_pk": {
|
"deck_terms_deck_id_term_id_pk": {
|
||||||
"name": "deck_terms_deck_id_term_id_pk",
|
"name": "deck_terms_deck_id_term_id_pk",
|
||||||
"columns": [
|
"columns": ["deck_id", "term_id"]
|
||||||
"deck_id",
|
|
||||||
"term_id"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"uniqueConstraints": {},
|
"uniqueConstraints": {},
|
||||||
|
|
@ -265,10 +250,7 @@
|
||||||
"unique_deck_name": {
|
"unique_deck_name": {
|
||||||
"name": "unique_deck_name",
|
"name": "unique_deck_name",
|
||||||
"nullsNotDistinct": false,
|
"nullsNotDistinct": false,
|
||||||
"columns": [
|
"columns": ["name", "source_language"]
|
||||||
"name",
|
|
||||||
"source_language"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"policies": {},
|
"policies": {},
|
||||||
|
|
@ -368,12 +350,8 @@
|
||||||
"name": "session_user_id_user_id_fk",
|
"name": "session_user_id_user_id_fk",
|
||||||
"tableFrom": "session",
|
"tableFrom": "session",
|
||||||
"tableTo": "user",
|
"tableTo": "user",
|
||||||
"columnsFrom": [
|
"columnsFrom": ["user_id"],
|
||||||
"user_id"
|
"columnsTo": ["id"],
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "cascade",
|
"onDelete": "cascade",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
}
|
}
|
||||||
|
|
@ -383,9 +361,7 @@
|
||||||
"session_token_unique": {
|
"session_token_unique": {
|
||||||
"name": "session_token_unique",
|
"name": "session_token_unique",
|
||||||
"nullsNotDistinct": false,
|
"nullsNotDistinct": false,
|
||||||
"columns": [
|
"columns": ["token"]
|
||||||
"token"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"policies": {},
|
"policies": {},
|
||||||
|
|
@ -435,12 +411,8 @@
|
||||||
"name": "term_glosses_term_id_terms_id_fk",
|
"name": "term_glosses_term_id_terms_id_fk",
|
||||||
"tableFrom": "term_glosses",
|
"tableFrom": "term_glosses",
|
||||||
"tableTo": "terms",
|
"tableTo": "terms",
|
||||||
"columnsFrom": [
|
"columnsFrom": ["term_id"],
|
||||||
"term_id"
|
"columnsTo": ["id"],
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "cascade",
|
"onDelete": "cascade",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
}
|
}
|
||||||
|
|
@ -450,10 +422,7 @@
|
||||||
"unique_term_gloss": {
|
"unique_term_gloss": {
|
||||||
"name": "unique_term_gloss",
|
"name": "unique_term_gloss",
|
||||||
"nullsNotDistinct": false,
|
"nullsNotDistinct": false,
|
||||||
"columns": [
|
"columns": ["term_id", "language_code"]
|
||||||
"term_id",
|
|
||||||
"language_code"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"policies": {},
|
"policies": {},
|
||||||
|
|
@ -488,12 +457,8 @@
|
||||||
"name": "term_topics_term_id_terms_id_fk",
|
"name": "term_topics_term_id_terms_id_fk",
|
||||||
"tableFrom": "term_topics",
|
"tableFrom": "term_topics",
|
||||||
"tableTo": "terms",
|
"tableTo": "terms",
|
||||||
"columnsFrom": [
|
"columnsFrom": ["term_id"],
|
||||||
"term_id"
|
"columnsTo": ["id"],
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "cascade",
|
"onDelete": "cascade",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
},
|
},
|
||||||
|
|
@ -501,12 +466,8 @@
|
||||||
"name": "term_topics_topic_id_topics_id_fk",
|
"name": "term_topics_topic_id_topics_id_fk",
|
||||||
"tableFrom": "term_topics",
|
"tableFrom": "term_topics",
|
||||||
"tableTo": "topics",
|
"tableTo": "topics",
|
||||||
"columnsFrom": [
|
"columnsFrom": ["topic_id"],
|
||||||
"topic_id"
|
"columnsTo": ["id"],
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "cascade",
|
"onDelete": "cascade",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
}
|
}
|
||||||
|
|
@ -514,10 +475,7 @@
|
||||||
"compositePrimaryKeys": {
|
"compositePrimaryKeys": {
|
||||||
"term_topics_term_id_topic_id_pk": {
|
"term_topics_term_id_topic_id_pk": {
|
||||||
"name": "term_topics_term_id_topic_id_pk",
|
"name": "term_topics_term_id_topic_id_pk",
|
||||||
"columns": [
|
"columns": ["term_id", "topic_id"]
|
||||||
"term_id",
|
|
||||||
"topic_id"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"uniqueConstraints": {},
|
"uniqueConstraints": {},
|
||||||
|
|
@ -591,10 +549,7 @@
|
||||||
"unique_source_id": {
|
"unique_source_id": {
|
||||||
"name": "unique_source_id",
|
"name": "unique_source_id",
|
||||||
"nullsNotDistinct": false,
|
"nullsNotDistinct": false,
|
||||||
"columns": [
|
"columns": ["source", "source_id"]
|
||||||
"source",
|
|
||||||
"source_id"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"policies": {},
|
"policies": {},
|
||||||
|
|
@ -650,9 +605,7 @@
|
||||||
"topics_slug_unique": {
|
"topics_slug_unique": {
|
||||||
"name": "topics_slug_unique",
|
"name": "topics_slug_unique",
|
||||||
"nullsNotDistinct": false,
|
"nullsNotDistinct": false,
|
||||||
"columns": [
|
"columns": ["slug"]
|
||||||
"slug"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"policies": {},
|
"policies": {},
|
||||||
|
|
@ -748,12 +701,8 @@
|
||||||
"name": "translations_term_id_terms_id_fk",
|
"name": "translations_term_id_terms_id_fk",
|
||||||
"tableFrom": "translations",
|
"tableFrom": "translations",
|
||||||
"tableTo": "terms",
|
"tableTo": "terms",
|
||||||
"columnsFrom": [
|
"columnsFrom": ["term_id"],
|
||||||
"term_id"
|
"columnsTo": ["id"],
|
||||||
],
|
|
||||||
"columnsTo": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"onDelete": "cascade",
|
"onDelete": "cascade",
|
||||||
"onUpdate": "no action"
|
"onUpdate": "no action"
|
||||||
}
|
}
|
||||||
|
|
@ -763,11 +712,7 @@
|
||||||
"unique_translations": {
|
"unique_translations": {
|
||||||
"name": "unique_translations",
|
"name": "unique_translations",
|
||||||
"nullsNotDistinct": false,
|
"nullsNotDistinct": false,
|
||||||
"columns": [
|
"columns": ["term_id", "language_code", "text"]
|
||||||
"term_id",
|
|
||||||
"language_code",
|
|
||||||
"text"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"policies": {},
|
"policies": {},
|
||||||
|
|
@ -844,9 +789,7 @@
|
||||||
"user_email_unique": {
|
"user_email_unique": {
|
||||||
"name": "user_email_unique",
|
"name": "user_email_unique",
|
||||||
"nullsNotDistinct": false,
|
"nullsNotDistinct": false,
|
||||||
"columns": [
|
"columns": ["email"]
|
||||||
"email"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"policies": {},
|
"policies": {},
|
||||||
|
|
@ -903,23 +846,17 @@
|
||||||
"users_openauth_sub_unique": {
|
"users_openauth_sub_unique": {
|
||||||
"name": "users_openauth_sub_unique",
|
"name": "users_openauth_sub_unique",
|
||||||
"nullsNotDistinct": false,
|
"nullsNotDistinct": false,
|
||||||
"columns": [
|
"columns": ["openauth_sub"]
|
||||||
"openauth_sub"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"users_email_unique": {
|
"users_email_unique": {
|
||||||
"name": "users_email_unique",
|
"name": "users_email_unique",
|
||||||
"nullsNotDistinct": false,
|
"nullsNotDistinct": false,
|
||||||
"columns": [
|
"columns": ["email"]
|
||||||
"email"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"users_display_name_unique": {
|
"users_display_name_unique": {
|
||||||
"name": "users_display_name_unique",
|
"name": "users_display_name_unique",
|
||||||
"nullsNotDistinct": false,
|
"nullsNotDistinct": false,
|
||||||
"columns": [
|
"columns": ["display_name"]
|
||||||
"display_name"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"policies": {},
|
"policies": {},
|
||||||
|
|
@ -1000,9 +937,5 @@
|
||||||
"roles": {},
|
"roles": {},
|
||||||
"policies": {},
|
"policies": {},
|
||||||
"views": {},
|
"views": {},
|
||||||
"_meta": {
|
"_meta": { "columns": {}, "schemas": {}, "tables": {} }
|
||||||
"columns": {},
|
}
|
||||||
"schemas": {},
|
|
||||||
"tables": {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -38,4 +38,4 @@
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"name": "@glossa/db",
|
"name": "@lila/db",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|
@ -11,7 +11,7 @@
|
||||||
"db:build-deck": "npx tsx src/generating-deck.ts"
|
"db:build-deck": "npx tsx src/generating-deck.ts"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@glossa/shared": "workspace:*",
|
"@lila/shared": "workspace:*",
|
||||||
"dotenv": "^17.3.1",
|
"dotenv": "^17.3.1",
|
||||||
"drizzle-orm": "^0.45.1",
|
"drizzle-orm": "^0.45.1",
|
||||||
"pg": "^8.20.0",
|
"pg": "^8.20.0",
|
||||||
|
|
|
||||||
|
|
@ -21,9 +21,9 @@ import {
|
||||||
SUPPORTED_POS,
|
SUPPORTED_POS,
|
||||||
CEFR_LEVELS,
|
CEFR_LEVELS,
|
||||||
DIFFICULTY_LEVELS,
|
DIFFICULTY_LEVELS,
|
||||||
} from "@glossa/shared";
|
} from "@lila/shared";
|
||||||
import { db } from "@glossa/db";
|
import { db } from "@lila/db";
|
||||||
import { terms, translations } from "@glossa/db/schema";
|
import { terms, translations } from "@lila/db/schema";
|
||||||
|
|
||||||
type POS = (typeof SUPPORTED_POS)[number];
|
type POS = (typeof SUPPORTED_POS)[number];
|
||||||
type LanguageCode = (typeof SUPPORTED_LANGUAGE_CODES)[number];
|
type LanguageCode = (typeof SUPPORTED_LANGUAGE_CODES)[number];
|
||||||
|
|
@ -165,7 +165,7 @@ async function checkCoverage(language: LanguageCode): Promise<void> {
|
||||||
|
|
||||||
const main = async () => {
|
const main = async () => {
|
||||||
console.log("##########################################");
|
console.log("##########################################");
|
||||||
console.log("Glossa — CEFR Coverage Check");
|
console.log("lila — CEFR Coverage Check");
|
||||||
console.log("##########################################");
|
console.log("##########################################");
|
||||||
|
|
||||||
for (const language of SUPPORTED_LANGUAGE_CODES) {
|
for (const language of SUPPORTED_LANGUAGE_CODES) {
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ import {
|
||||||
CEFR_LEVELS,
|
CEFR_LEVELS,
|
||||||
SUPPORTED_DECK_TYPES,
|
SUPPORTED_DECK_TYPES,
|
||||||
DIFFICULTY_LEVELS,
|
DIFFICULTY_LEVELS,
|
||||||
} from "@glossa/shared";
|
} from "@lila/shared";
|
||||||
|
|
||||||
export const terms = pgTable(
|
export const terms = pgTable(
|
||||||
"terms",
|
"terms",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import fs from "node:fs/promises";
|
import fs from "node:fs/promises";
|
||||||
import { db } from "@glossa/db";
|
import { db } from "@lila/db";
|
||||||
import { translations, terms, decks, deck_terms } from "@glossa/db/schema";
|
import { translations, terms, decks, deck_terms } from "@lila/db/schema";
|
||||||
import { inArray, and, eq, ne, countDistinct } from "drizzle-orm";
|
import { inArray, and, eq, ne, countDistinct } from "drizzle-orm";
|
||||||
|
|
||||||
type DbOrTx = Parameters<Parameters<typeof db.transaction>[0]>[0];
|
type DbOrTx = Parameters<Parameters<typeof db.transaction>[0]>[0];
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
import { db } from "@glossa/db";
|
import { db } from "@lila/db";
|
||||||
import { eq, and, isNotNull, sql, ne } from "drizzle-orm";
|
import { eq, and, isNotNull, sql, ne } from "drizzle-orm";
|
||||||
import { terms, translations, term_glosses } from "@glossa/db/schema";
|
import { terms, translations, term_glosses } from "@lila/db/schema";
|
||||||
import { alias } from "drizzle-orm/pg-core";
|
import { alias } from "drizzle-orm/pg-core";
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
SupportedLanguageCode,
|
SupportedLanguageCode,
|
||||||
SupportedPos,
|
SupportedPos,
|
||||||
DifficultyLevel,
|
DifficultyLevel,
|
||||||
} from "@glossa/shared";
|
} from "@lila/shared";
|
||||||
|
|
||||||
export type TranslationPairRow = {
|
export type TranslationPairRow = {
|
||||||
termId: string;
|
termId: string;
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,9 @@ import {
|
||||||
SUPPORTED_POS,
|
SUPPORTED_POS,
|
||||||
CEFR_LEVELS,
|
CEFR_LEVELS,
|
||||||
DIFFICULTY_LEVELS,
|
DIFFICULTY_LEVELS,
|
||||||
} from "@glossa/shared";
|
} from "@lila/shared";
|
||||||
import { db } from "@glossa/db";
|
import { db } from "@lila/db";
|
||||||
import { translations, terms } from "@glossa/db/schema";
|
import { translations, terms } from "@lila/db/schema";
|
||||||
|
|
||||||
type POS = (typeof SUPPORTED_POS)[number];
|
type POS = (typeof SUPPORTED_POS)[number];
|
||||||
type LanguageCode = (typeof SUPPORTED_LANGUAGE_CODES)[number];
|
type LanguageCode = (typeof SUPPORTED_LANGUAGE_CODES)[number];
|
||||||
|
|
@ -130,7 +130,7 @@ async function enrichLanguage(language: LanguageCode): Promise<void> {
|
||||||
|
|
||||||
const main = async () => {
|
const main = async () => {
|
||||||
console.log("##########################################");
|
console.log("##########################################");
|
||||||
console.log("Glossa — CEFR Enrichment");
|
console.log("lila — CEFR Enrichment");
|
||||||
console.log("##########################################\n");
|
console.log("##########################################\n");
|
||||||
|
|
||||||
for (const lang of SUPPORTED_LANGUAGE_CODES) {
|
for (const lang of SUPPORTED_LANGUAGE_CODES) {
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
import fs from "node:fs/promises";
|
import fs from "node:fs/promises";
|
||||||
import { and, count, eq, inArray } from "drizzle-orm";
|
import { and, count, eq, inArray } from "drizzle-orm";
|
||||||
|
|
||||||
import { SUPPORTED_LANGUAGE_CODES, SUPPORTED_POS } from "@glossa/shared";
|
import { SUPPORTED_LANGUAGE_CODES, SUPPORTED_POS } from "@lila/shared";
|
||||||
import { db } from "@glossa/db";
|
import { db } from "@lila/db";
|
||||||
import { terms, translations, term_glosses } from "@glossa/db/schema";
|
import { terms, translations, term_glosses } from "@lila/db/schema";
|
||||||
|
|
||||||
type POS = (typeof SUPPORTED_POS)[number];
|
type POS = (typeof SUPPORTED_POS)[number];
|
||||||
type LanguageCode = (typeof SUPPORTED_LANGUAGE_CODES)[number];
|
type LanguageCode = (typeof SUPPORTED_LANGUAGE_CODES)[number];
|
||||||
|
|
@ -129,7 +129,7 @@ async function processBatch(batch: SynsetRecord[]): Promise<void> {
|
||||||
|
|
||||||
const main = async () => {
|
const main = async () => {
|
||||||
console.log("\n##########################################");
|
console.log("\n##########################################");
|
||||||
console.log("Glossa — OMW seed");
|
console.log("lila — OMW seed");
|
||||||
console.log("##########################################\n");
|
console.log("##########################################\n");
|
||||||
|
|
||||||
// One file per POS — names are derived from SUPPORTED_POS so adding a new
|
// One file per POS — names are derived from SUPPORTED_POS so adding a new
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"name": "@glossa/shared",
|
"name": "@lila/shared",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|
|
||||||
8
pnpm-lock.yaml
generated
8
pnpm-lock.yaml
generated
|
|
@ -47,10 +47,10 @@ importers:
|
||||||
|
|
||||||
apps/api:
|
apps/api:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@glossa/db':
|
'@lila/db':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../../packages/db
|
version: link:../../packages/db
|
||||||
'@glossa/shared':
|
'@lila/shared':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../../packages/shared
|
version: link:../../packages/shared
|
||||||
better-auth:
|
better-auth:
|
||||||
|
|
@ -81,7 +81,7 @@ importers:
|
||||||
|
|
||||||
apps/web:
|
apps/web:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@glossa/shared':
|
'@lila/shared':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../../packages/shared
|
version: link:../../packages/shared
|
||||||
'@tailwindcss/vite':
|
'@tailwindcss/vite':
|
||||||
|
|
@ -130,7 +130,7 @@ importers:
|
||||||
|
|
||||||
packages/db:
|
packages/db:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@glossa/shared':
|
'@lila/shared':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../shared
|
version: link:../shared
|
||||||
dotenv:
|
dotenv:
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ This directory contains the source data files and extraction/merge pipeline for
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
The pipeline transforms raw vocabulary data from multiple sources into a standardized format, resolves conflicts between sources, and produces an authoritative CEFR dataset per language. This dataset is then used by the Glossa database package to update translation records.
|
The pipeline transforms raw vocabulary data from multiple sources into a standardized format, resolves conflicts between sources, and produces an authoritative CEFR dataset per language. This dataset is then used by the lila database package to update translation records.
|
||||||
|
|
||||||
## Supported Languages
|
## Supported Languages
|
||||||
|
|
||||||
|
|
@ -195,7 +195,7 @@ To add a new source:
|
||||||
|
|
||||||
## Constants and Constraints
|
## Constants and Constraints
|
||||||
|
|
||||||
The pipeline respects these constraints from the Glossa shared constants:
|
The pipeline respects these constraints from the lila shared constants:
|
||||||
|
|
||||||
- **Supported languages:** en, it
|
- **Supported languages:** en, it
|
||||||
- **Supported parts of speech:** noun, verb
|
- **Supported parts of speech:** noun, verb
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ import csv
|
||||||
import json
|
import json
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
# Constants matching @glossa/shared
|
# Constants matching @lila/shared
|
||||||
SUPPORTED_POS = ["noun", "verb"]
|
SUPPORTED_POS = ["noun", "verb"]
|
||||||
CEFR_LEVELS = ["A1", "A2", "B1", "B2", "C1", "C2"]
|
CEFR_LEVELS = ["A1", "A2", "B1", "B2", "C1", "C2"]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ from pathlib import Path
|
||||||
|
|
||||||
import xlrd
|
import xlrd
|
||||||
|
|
||||||
# Constants matching @glossa/shared
|
# Constants matching @lila/shared
|
||||||
SUPPORTED_POS = ["noun", "verb"]
|
SUPPORTED_POS = ["noun", "verb"]
|
||||||
CEFR_LEVELS = ["A1", "A2", "B1", "B2", "C1", "C2"]
|
CEFR_LEVELS = ["A1", "A2", "B1", "B2", "C1", "C2"]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ import csv
|
||||||
import json
|
import json
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
# Constants matching @glossa/shared
|
# Constants matching @lila/shared
|
||||||
SUPPORTED_POS = ["noun", "verb"]
|
SUPPORTED_POS = ["noun", "verb"]
|
||||||
CEFR_LEVELS = ["A1", "A2", "B1", "B2", "C1", "C2"]
|
CEFR_LEVELS = ["A1", "A2", "B1", "B2", "C1", "C2"]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ Output format (normalized):
|
||||||
import json
|
import json
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
# Constants matching @glossa/shared
|
# Constants matching @lila/shared
|
||||||
SUPPORTED_POS = ["noun", "verb"]
|
SUPPORTED_POS = ["noun", "verb"]
|
||||||
CEFR_LEVELS = ["A1", "A2", "B1", "B2", "C1", "C2"]
|
CEFR_LEVELS = ["A1", "A2", "B1", "B2", "C1", "C2"]
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue