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",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
|
@ -10,8 +10,8 @@
|
|||
"test": "vitest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@glossa/db": "workspace:*",
|
||||
"@glossa/shared": "workspace:*",
|
||||
"@lila/db": "workspace:*",
|
||||
"@lila/shared": "workspace:*",
|
||||
"better-auth": "^1.6.2",
|
||||
"cors": "^2.8.6",
|
||||
"express": "^5.2.1"
|
||||
|
|
|
|||
|
|
@ -1,12 +1,9 @@
|
|||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import request from "supertest";
|
||||
|
||||
vi.mock("@glossa/db", () => ({
|
||||
getGameTerms: vi.fn(),
|
||||
getDistractors: vi.fn(),
|
||||
}));
|
||||
vi.mock("@lila/db", () => ({ getGameTerms: vi.fn(), getDistractors: vi.fn() }));
|
||||
|
||||
import { getGameTerms, getDistractors } from "@glossa/db";
|
||||
import { getGameTerms, getDistractors } from "@lila/db";
|
||||
import { createApp } from "../app.js";
|
||||
|
||||
const app = createApp();
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
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 { ValidationError } from "../errors/AppError.js";
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { betterAuth } from "better-auth";
|
||||
import { drizzleAdapter } from "better-auth/adapters/drizzle";
|
||||
import { db } from "@glossa/db";
|
||||
import * as schema from "@glossa/db/schema";
|
||||
import { db } from "@lila/db";
|
||||
import * as schema from "@lila/db/schema";
|
||||
|
||||
export const auth = betterAuth({
|
||||
database: drizzleAdapter(db, { provider: "pg", schema }),
|
||||
|
|
|
|||
|
|
@ -1,12 +1,9 @@
|
|||
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", () => ({
|
||||
getGameTerms: vi.fn(),
|
||||
getDistractors: vi.fn(),
|
||||
}));
|
||||
vi.mock("@lila/db", () => ({ getGameTerms: vi.fn(), getDistractors: vi.fn() }));
|
||||
|
||||
import { getGameTerms, getDistractors } from "@glossa/db";
|
||||
import { getGameTerms, getDistractors } from "@lila/db";
|
||||
import { createGameSession, evaluateAnswer } from "./gameService.js";
|
||||
|
||||
const mockGetGameTerms = vi.mocked(getGameTerms);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { randomUUID } from "crypto";
|
||||
import { getGameTerms, getDistractors } from "@glossa/db";
|
||||
import { getGameTerms, getDistractors } from "@lila/db";
|
||||
import type {
|
||||
GameRequest,
|
||||
GameSession,
|
||||
|
|
@ -7,7 +7,7 @@ import type {
|
|||
AnswerOption,
|
||||
AnswerSubmission,
|
||||
AnswerResult,
|
||||
} from "@glossa/shared";
|
||||
} from "@lila/shared";
|
||||
import { InMemoryGameSessionStore } from "../gameSessionStore/index.js";
|
||||
import { NotFoundError } from "../errors/AppError.js";
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>glossa</title>
|
||||
<title>lila</title>
|
||||
<!--TODO: add favicon-->
|
||||
<link rel="icon" href="data:," />
|
||||
</head>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"name": "@glossa/web",
|
||||
"name": "@lila/web",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
|
|
@ -9,7 +9,7 @@
|
|||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@glossa/shared": "workspace:*",
|
||||
"@lila/shared": "workspace:*",
|
||||
"@tailwindcss/vite": "^4.2.2",
|
||||
"@tanstack/react-router": "^1.168.1",
|
||||
"@tanstack/react-router-devtools": "^1.166.10",
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ import {
|
|||
SUPPORTED_POS,
|
||||
DIFFICULTY_LEVELS,
|
||||
GAME_ROUNDS,
|
||||
} from "@glossa/shared";
|
||||
import type { GameRequest } from "@glossa/shared";
|
||||
} from "@lila/shared";
|
||||
import type { GameRequest } from "@lila/shared";
|
||||
|
||||
const LABELS: Record<string, string> = {
|
||||
en: "English",
|
||||
|
|
@ -92,7 +92,7 @@ export const GameSetup = ({ onStart }: GameSetupProps) => {
|
|||
return (
|
||||
<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">
|
||||
<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>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { useState } from "react";
|
||||
import type { GameQuestion, AnswerResult } from "@glossa/shared";
|
||||
import type { GameQuestion, AnswerResult } from "@lila/shared";
|
||||
import { OptionButton } from "./OptionButton";
|
||||
|
||||
type QuestionCardProps = {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { AnswerResult } from "@glossa/shared";
|
||||
import type { AnswerResult } from "@lila/shared";
|
||||
|
||||
type ScoreScreenProps = { results: AnswerResult[]; onPlayAgain: () => void };
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ const LoginPage = () => {
|
|||
|
||||
return (
|
||||
<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
|
||||
className="w-64 rounded bg-gray-800 px-4 py-2 text-white hover:bg-gray-700"
|
||||
onClick={() =>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { createFileRoute, redirect } from "@tanstack/react-router";
|
||||
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 { ScoreScreen } from "../components/game/ScoreScreen";
|
||||
import { GameSetup } from "../components/game/GameSetup";
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
"noEmit": true,
|
||||
"target": "ES2023",
|
||||
"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:
|
||||
database:
|
||||
container_name: glossa-database
|
||||
container_name: lila-database
|
||||
image: postgres:18.3-alpine3.23
|
||||
env_file:
|
||||
- .env
|
||||
|
|
@ -9,7 +9,7 @@ services:
|
|||
ports:
|
||||
- "5432:5432"
|
||||
volumes:
|
||||
- glossa-db:/var/lib/postgresql/data
|
||||
- lila-db:/var/lib/postgresql/data
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
|
||||
|
|
@ -18,7 +18,7 @@ services:
|
|||
retries: 5
|
||||
|
||||
valkey:
|
||||
container_name: glossa-valkey
|
||||
container_name: lila-valkey
|
||||
image: valkey/valkey:9.1-alpine3.23
|
||||
ports:
|
||||
- "6379:6379"
|
||||
|
|
@ -30,7 +30,7 @@ services:
|
|||
retries: 5
|
||||
|
||||
api:
|
||||
container_name: glossa-api
|
||||
container_name: lila-api
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ./apps/api/Dockerfile
|
||||
|
|
@ -57,7 +57,7 @@ services:
|
|||
condition: service_healthy
|
||||
|
||||
web:
|
||||
container_name: glossa-web
|
||||
container_name: lila-web
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ./apps/web/Dockerfile
|
||||
|
|
@ -75,4 +75,4 @@ services:
|
|||
condition: service_healthy
|
||||
|
||||
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)
|
||||
|
||||
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
|
||||
|
||||
|
|
@ -322,7 +322,7 @@ The `exports` field must be an object, not an array:
|
|||
|
||||
## 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.
|
||||
|
||||
|
|
|
|||
|
|
@ -19,8 +19,8 @@ Progress: resolved 577, reused 0, downloaded 0, added 0, done
|
|||
WARN Issues with peer dependencies found
|
||||
.
|
||||
└─┬ 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
|
||||
. | +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 +
|
||||
Done in 5.6s using pnpm v10.33.0
|
||||
|
||||
### env managing
|
||||
|
|
@ -43,7 +43,7 @@ app publication/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.).
|
||||
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).
|
||||
Manage App Audience Configuration
|
||||
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
|
||||
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)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Glossa — Roadmap
|
||||
# lila — Roadmap
|
||||
|
||||
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.
|
||||
|
||||
### Data pipeline
|
||||
|
||||
- [x] Run `extract-en-it-nouns.py` locally → generates JSON
|
||||
- [x] Write Drizzle schema: `terms`, `translations`, `term_glosses`, `decks`, `deck_terms`
|
||||
- [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
|
||||
|
||||
### Schemas
|
||||
|
||||
- [x] Define `GameRequestSchema` in `packages/shared`
|
||||
- [x] Define `AnswerOption`, `GameQuestion`, `GameSession`, `AnswerSubmission`, `AnswerResult` schemas
|
||||
- [x] Derived types exported from constants (`SupportedLanguageCode`, `SupportedPos`, `DifficultyLevel`)
|
||||
|
||||
### Model layer
|
||||
|
||||
- [x] `getGameTerms()` with POS / language / difficulty / limit filters
|
||||
- [x] Double join on `translations` (source + target language)
|
||||
- [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`
|
||||
|
||||
### Service layer
|
||||
|
||||
- [x] `createGameSession()` — fetches terms, fetches distractors, shuffles options, stores session
|
||||
- [x] `evaluateAnswer()` — looks up session, compares submitted optionId to stored correct answer
|
||||
- [x] `GameSessionStore` interface + `InMemoryGameSessionStore` (swappable to Valkey)
|
||||
|
||||
### API endpoints
|
||||
|
||||
- [x] `POST /api/v1/game/start` — route, controller, service
|
||||
- [x] `POST /api/v1/game/answer` — route, controller, service
|
||||
- [x] End-to-end pipeline verified with test script
|
||||
|
||||
### Error handling
|
||||
|
||||
- [x] Typed error classes: `AppError`, `ValidationError` (400), `NotFoundError` (404)
|
||||
- [x] Central error middleware in `app.ts`
|
||||
- [x] Controllers cleaned up: validate → call service → `next(error)` on failure
|
||||
|
||||
### Tests
|
||||
|
||||
- [x] Unit tests for `createGameSession` (question shape, options, distractors, gloss)
|
||||
- [x] Unit tests for `evaluateAnswer` (correct, incorrect, missing session, missing question)
|
||||
- [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.**
|
||||
> 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 |
|
||||
| ------------------------------- | -------------------------------------- |
|
||||
| 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 |
|
||||
| Valkey / Redis cache | Only needed for multiplayer room state |
|
||||
| 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
|
||||
|
||||
| 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 |
|
||||
| Multiplayer Lobby | Room creation, join by code, WebSocket connection |
|
||||
| Multiplayer Game | Simultaneous answers, server timer, live scores, winner screen |
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
{
|
||||
"name": "glossa",
|
||||
"name": "lila",
|
||||
"version": "1.0.0",
|
||||
"description": "a vocabulary trainer",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "pnpm --filter @glossa/shared build && pnpm --filter @glossa/db build && pnpm --filter @glossa/api build",
|
||||
"dev": "concurrently --names \"api,web\" -c \"magenta.bold,green.bold\" \"pnpm --filter @glossa/api dev\" \"pnpm --filter @glossa/web dev\"",
|
||||
"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 @lila/api dev\" \"pnpm --filter @lila/web dev\"",
|
||||
"test": "vitest",
|
||||
"test:run": "vitest run",
|
||||
"lint": "eslint .",
|
||||
|
|
|
|||
|
|
@ -110,12 +110,8 @@
|
|||
"name": "account_user_id_user_id_fk",
|
||||
"tableFrom": "account",
|
||||
"tableTo": "user",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsFrom": ["user_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
|
|
@ -149,12 +145,8 @@
|
|||
"name": "deck_terms_deck_id_decks_id_fk",
|
||||
"tableFrom": "deck_terms",
|
||||
"tableTo": "decks",
|
||||
"columnsFrom": [
|
||||
"deck_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsFrom": ["deck_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
|
|
@ -162,12 +154,8 @@
|
|||
"name": "deck_terms_term_id_terms_id_fk",
|
||||
"tableFrom": "deck_terms",
|
||||
"tableTo": "terms",
|
||||
"columnsFrom": [
|
||||
"term_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsFrom": ["term_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
|
|
@ -175,10 +163,7 @@
|
|||
"compositePrimaryKeys": {
|
||||
"deck_terms_deck_id_term_id_pk": {
|
||||
"name": "deck_terms_deck_id_term_id_pk",
|
||||
"columns": [
|
||||
"deck_id",
|
||||
"term_id"
|
||||
]
|
||||
"columns": ["deck_id", "term_id"]
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
|
|
@ -265,10 +250,7 @@
|
|||
"unique_deck_name": {
|
||||
"name": "unique_deck_name",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"name",
|
||||
"source_language"
|
||||
]
|
||||
"columns": ["name", "source_language"]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
|
|
@ -368,12 +350,8 @@
|
|||
"name": "session_user_id_user_id_fk",
|
||||
"tableFrom": "session",
|
||||
"tableTo": "user",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsFrom": ["user_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
|
|
@ -383,9 +361,7 @@
|
|||
"session_token_unique": {
|
||||
"name": "session_token_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"token"
|
||||
]
|
||||
"columns": ["token"]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
|
|
@ -435,12 +411,8 @@
|
|||
"name": "term_glosses_term_id_terms_id_fk",
|
||||
"tableFrom": "term_glosses",
|
||||
"tableTo": "terms",
|
||||
"columnsFrom": [
|
||||
"term_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsFrom": ["term_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
|
|
@ -450,10 +422,7 @@
|
|||
"unique_term_gloss": {
|
||||
"name": "unique_term_gloss",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"term_id",
|
||||
"language_code"
|
||||
]
|
||||
"columns": ["term_id", "language_code"]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
|
|
@ -488,12 +457,8 @@
|
|||
"name": "term_topics_term_id_terms_id_fk",
|
||||
"tableFrom": "term_topics",
|
||||
"tableTo": "terms",
|
||||
"columnsFrom": [
|
||||
"term_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsFrom": ["term_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
|
|
@ -501,12 +466,8 @@
|
|||
"name": "term_topics_topic_id_topics_id_fk",
|
||||
"tableFrom": "term_topics",
|
||||
"tableTo": "topics",
|
||||
"columnsFrom": [
|
||||
"topic_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsFrom": ["topic_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
|
|
@ -514,10 +475,7 @@
|
|||
"compositePrimaryKeys": {
|
||||
"term_topics_term_id_topic_id_pk": {
|
||||
"name": "term_topics_term_id_topic_id_pk",
|
||||
"columns": [
|
||||
"term_id",
|
||||
"topic_id"
|
||||
]
|
||||
"columns": ["term_id", "topic_id"]
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
|
|
@ -591,10 +549,7 @@
|
|||
"unique_source_id": {
|
||||
"name": "unique_source_id",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"source",
|
||||
"source_id"
|
||||
]
|
||||
"columns": ["source", "source_id"]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
|
|
@ -650,9 +605,7 @@
|
|||
"topics_slug_unique": {
|
||||
"name": "topics_slug_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"slug"
|
||||
]
|
||||
"columns": ["slug"]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
|
|
@ -748,12 +701,8 @@
|
|||
"name": "translations_term_id_terms_id_fk",
|
||||
"tableFrom": "translations",
|
||||
"tableTo": "terms",
|
||||
"columnsFrom": [
|
||||
"term_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"columnsFrom": ["term_id"],
|
||||
"columnsTo": ["id"],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
|
|
@ -763,11 +712,7 @@
|
|||
"unique_translations": {
|
||||
"name": "unique_translations",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"term_id",
|
||||
"language_code",
|
||||
"text"
|
||||
]
|
||||
"columns": ["term_id", "language_code", "text"]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
|
|
@ -844,9 +789,7 @@
|
|||
"user_email_unique": {
|
||||
"name": "user_email_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"email"
|
||||
]
|
||||
"columns": ["email"]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
|
|
@ -903,23 +846,17 @@
|
|||
"users_openauth_sub_unique": {
|
||||
"name": "users_openauth_sub_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"openauth_sub"
|
||||
]
|
||||
"columns": ["openauth_sub"]
|
||||
},
|
||||
"users_email_unique": {
|
||||
"name": "users_email_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"email"
|
||||
]
|
||||
"columns": ["email"]
|
||||
},
|
||||
"users_display_name_unique": {
|
||||
"name": "users_display_name_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"display_name"
|
||||
]
|
||||
"columns": ["display_name"]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
|
|
@ -1000,9 +937,5 @@
|
|||
"roles": {},
|
||||
"policies": {},
|
||||
"views": {},
|
||||
"_meta": {
|
||||
"columns": {},
|
||||
"schemas": {},
|
||||
"tables": {}
|
||||
}
|
||||
}
|
||||
"_meta": { "columns": {}, "schemas": {}, "tables": {} }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,4 +38,4 @@
|
|||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"name": "@glossa/db",
|
||||
"name": "@lila/db",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
|
@ -11,7 +11,7 @@
|
|||
"db:build-deck": "npx tsx src/generating-deck.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@glossa/shared": "workspace:*",
|
||||
"@lila/shared": "workspace:*",
|
||||
"dotenv": "^17.3.1",
|
||||
"drizzle-orm": "^0.45.1",
|
||||
"pg": "^8.20.0",
|
||||
|
|
|
|||
|
|
@ -21,9 +21,9 @@ import {
|
|||
SUPPORTED_POS,
|
||||
CEFR_LEVELS,
|
||||
DIFFICULTY_LEVELS,
|
||||
} from "@glossa/shared";
|
||||
import { db } from "@glossa/db";
|
||||
import { terms, translations } from "@glossa/db/schema";
|
||||
} from "@lila/shared";
|
||||
import { db } from "@lila/db";
|
||||
import { terms, translations } from "@lila/db/schema";
|
||||
|
||||
type POS = (typeof SUPPORTED_POS)[number];
|
||||
type LanguageCode = (typeof SUPPORTED_LANGUAGE_CODES)[number];
|
||||
|
|
@ -165,7 +165,7 @@ async function checkCoverage(language: LanguageCode): Promise<void> {
|
|||
|
||||
const main = async () => {
|
||||
console.log("##########################################");
|
||||
console.log("Glossa — CEFR Coverage Check");
|
||||
console.log("lila — CEFR Coverage Check");
|
||||
console.log("##########################################");
|
||||
|
||||
for (const language of SUPPORTED_LANGUAGE_CODES) {
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ import {
|
|||
CEFR_LEVELS,
|
||||
SUPPORTED_DECK_TYPES,
|
||||
DIFFICULTY_LEVELS,
|
||||
} from "@glossa/shared";
|
||||
} from "@lila/shared";
|
||||
|
||||
export const terms = pgTable(
|
||||
"terms",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import fs from "node:fs/promises";
|
||||
import { db } from "@glossa/db";
|
||||
import { translations, terms, decks, deck_terms } from "@glossa/db/schema";
|
||||
import { db } from "@lila/db";
|
||||
import { translations, terms, decks, deck_terms } from "@lila/db/schema";
|
||||
import { inArray, and, eq, ne, countDistinct } from "drizzle-orm";
|
||||
|
||||
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 { 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 type {
|
||||
SupportedLanguageCode,
|
||||
SupportedPos,
|
||||
DifficultyLevel,
|
||||
} from "@glossa/shared";
|
||||
} from "@lila/shared";
|
||||
|
||||
export type TranslationPairRow = {
|
||||
termId: string;
|
||||
|
|
|
|||
|
|
@ -6,9 +6,9 @@ import {
|
|||
SUPPORTED_POS,
|
||||
CEFR_LEVELS,
|
||||
DIFFICULTY_LEVELS,
|
||||
} from "@glossa/shared";
|
||||
import { db } from "@glossa/db";
|
||||
import { translations, terms } from "@glossa/db/schema";
|
||||
} from "@lila/shared";
|
||||
import { db } from "@lila/db";
|
||||
import { translations, terms } from "@lila/db/schema";
|
||||
|
||||
type POS = (typeof SUPPORTED_POS)[number];
|
||||
type LanguageCode = (typeof SUPPORTED_LANGUAGE_CODES)[number];
|
||||
|
|
@ -130,7 +130,7 @@ async function enrichLanguage(language: LanguageCode): Promise<void> {
|
|||
|
||||
const main = async () => {
|
||||
console.log("##########################################");
|
||||
console.log("Glossa — CEFR Enrichment");
|
||||
console.log("lila — CEFR Enrichment");
|
||||
console.log("##########################################\n");
|
||||
|
||||
for (const lang of SUPPORTED_LANGUAGE_CODES) {
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import fs from "node:fs/promises";
|
||||
import { and, count, eq, inArray } from "drizzle-orm";
|
||||
|
||||
import { SUPPORTED_LANGUAGE_CODES, SUPPORTED_POS } from "@glossa/shared";
|
||||
import { db } from "@glossa/db";
|
||||
import { terms, translations, term_glosses } from "@glossa/db/schema";
|
||||
import { SUPPORTED_LANGUAGE_CODES, SUPPORTED_POS } from "@lila/shared";
|
||||
import { db } from "@lila/db";
|
||||
import { terms, translations, term_glosses } from "@lila/db/schema";
|
||||
|
||||
type POS = (typeof SUPPORTED_POS)[number];
|
||||
type LanguageCode = (typeof SUPPORTED_LANGUAGE_CODES)[number];
|
||||
|
|
@ -129,7 +129,7 @@ async function processBatch(batch: SynsetRecord[]): Promise<void> {
|
|||
|
||||
const main = async () => {
|
||||
console.log("\n##########################################");
|
||||
console.log("Glossa — OMW seed");
|
||||
console.log("lila — OMW seed");
|
||||
console.log("##########################################\n");
|
||||
|
||||
// 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",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
|
|
|
|||
8
pnpm-lock.yaml
generated
8
pnpm-lock.yaml
generated
|
|
@ -47,10 +47,10 @@ importers:
|
|||
|
||||
apps/api:
|
||||
dependencies:
|
||||
'@glossa/db':
|
||||
'@lila/db':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/db
|
||||
'@glossa/shared':
|
||||
'@lila/shared':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/shared
|
||||
better-auth:
|
||||
|
|
@ -81,7 +81,7 @@ importers:
|
|||
|
||||
apps/web:
|
||||
dependencies:
|
||||
'@glossa/shared':
|
||||
'@lila/shared':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/shared
|
||||
'@tailwindcss/vite':
|
||||
|
|
@ -130,7 +130,7 @@ importers:
|
|||
|
||||
packages/db:
|
||||
dependencies:
|
||||
'@glossa/shared':
|
||||
'@lila/shared':
|
||||
specifier: workspace:*
|
||||
version: link:../shared
|
||||
dotenv:
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ This directory contains the source data files and extraction/merge pipeline for
|
|||
|
||||
## 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
|
||||
|
||||
|
|
@ -195,7 +195,7 @@ To add a new source:
|
|||
|
||||
## 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 parts of speech:** noun, verb
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import csv
|
|||
import json
|
||||
from pathlib import Path
|
||||
|
||||
# Constants matching @glossa/shared
|
||||
# Constants matching @lila/shared
|
||||
SUPPORTED_POS = ["noun", "verb"]
|
||||
CEFR_LEVELS = ["A1", "A2", "B1", "B2", "C1", "C2"]
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ from pathlib import Path
|
|||
|
||||
import xlrd
|
||||
|
||||
# Constants matching @glossa/shared
|
||||
# Constants matching @lila/shared
|
||||
SUPPORTED_POS = ["noun", "verb"]
|
||||
CEFR_LEVELS = ["A1", "A2", "B1", "B2", "C1", "C2"]
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import csv
|
|||
import json
|
||||
from pathlib import Path
|
||||
|
||||
# Constants matching @glossa/shared
|
||||
# Constants matching @lila/shared
|
||||
SUPPORTED_POS = ["noun", "verb"]
|
||||
CEFR_LEVELS = ["A1", "A2", "B1", "B2", "C1", "C2"]
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ Output format (normalized):
|
|||
import json
|
||||
from pathlib import Path
|
||||
|
||||
# Constants matching @glossa/shared
|
||||
# Constants matching @lila/shared
|
||||
SUPPORTED_POS = ["noun", "verb"]
|
||||
CEFR_LEVELS = ["A1", "A2", "B1", "B2", "C1", "C2"]
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue