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:
lila 2026-04-13 10:00:52 +02:00
parent 1699f78f0b
commit 3f7bc4111e
37 changed files with 116 additions and 182 deletions

View file

@ -1 +1 @@
# glossa # lila

View file

@ -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"

View file

@ -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();

View file

@ -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";

View file

@ -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 }),

View file

@ -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);

View file

@ -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";

View file

@ -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>

View file

@ -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",

View file

@ -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>

View file

@ -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 = {

View file

@ -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 };

View file

@ -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={() =>

View file

@ -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";

View file

@ -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"]
} }

View file

@ -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:

View file

@ -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.

View file

@ -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)

View file

@ -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)

View file

@ -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 |

View file

@ -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 .",

View file

@ -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": {}
}
}

View file

@ -38,4 +38,4 @@
"breakpoints": true "breakpoints": true
} }
] ]
} }

View file

@ -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",

View file

@ -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) {

View file

@ -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",

View file

@ -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];

View file

@ -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;

View file

@ -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) {

View file

@ -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

View file

@ -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
View file

@ -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:

View file

@ -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

View file

@ -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"]

View file

@ -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"]

View file

@ -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"]

View file

@ -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"]