import "reflect-metadata"; import { SAMPLE } from "@repo/common/sample"; import path from "path" import { fileURLToPath } from 'url'; import express from "express"; import crypto from "crypto"; import fs from "fs"; import { appDataSource } from "./config/dbconfig.js"; import { Lemma, WordForm, Lect, Definition, Example, Role, User } from "./db/dbmodel.js"; import "@total-typescript/ts-reset"; import { Like, In } from "typeorm"; import { BasicStrategy } from "passport-http"; import passport from "passport"; const RELOAD_SHEET_ON_START = false; const SOURCE_FILE = "res/sample.tsv"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); appDataSource .initialize() .then(async () => { initExpress(); if (RELOAD_SHEET_ON_START) { await loadSheet(); } await ensureDefaultRoles(); }) .catch((error) => console.log(error)); async function ensureDefaultRoles(){ const ADMIN_NAME = "admin"; const EDITOR_NAME = "editor"; const READER_NAME = "reader"; let admin_role: Role; let editor_role: Role; let reader_role: Role; // Initialize roles if they don't exist yet try { [admin_role, editor_role, reader_role] = await Promise.all([ Role.findOneBy({ role_name: ADMIN_NAME }).then(r => r || Role.save({ role_name: ADMIN_NAME })), Role.findOneBy({ role_name: EDITOR_NAME }).then(r => r || Role.save({ role_name: EDITOR_NAME })), Role.findOneBy({ role_name: READER_NAME }).then(r => r || Role.save({ role_name: READER_NAME })), ]); } catch (error) { console.error("Role initialization failed:", error); } // Initialize sample users let admin_user: User; let editor_user: User; let reader_user: User; try { [admin_user, editor_user, reader_user] = await Promise.all([ User.findOne({ where: { user_name: ADMIN_NAME } }) .then(u => u || User.save(generate_user(ADMIN_NAME, ADMIN_NAME, [admin_role]))), User.findOne({ where: { user_name: EDITOR_NAME } }) .then(u => u || User.save(generate_user(EDITOR_NAME, EDITOR_NAME, [editor_role]))), User.findOne({ where: { user_name: READER_NAME } }) .then(u => u || User.save(generate_user(READER_NAME, READER_NAME, [reader_role]))), ]); } catch (error) { console.error("User initialization failed:", error); } } function generate_user(user_name: string, password: string, roles:Role[]): User{ let user = new User(); user.user_name = user_name; user.roles = roles; user.setPassword(password); return user; } function initExpress() { const app = express(); const PORT = 1225; console.info(path.join(__dirname, '../../ktb-static')); app.use(express.json(), express.static(path.join(__dirname, '../../ktb-static'))); passport.use(new BasicStrategy( async function(user_name, password, cb) { const user = await User.findOneBy({user_name}); if(!user){ return cb(null, false); } if(!user.checkPassword(password)){ return cb(null, false); } return cb(null, user); } )); app.use(passport.initialize()); app.use(passport.session()); app.get("/login", passport.authenticate('basic', { session: false }), (_req, res) => { res.status(200).send(); }); app.get("/sample", (_req, res) => { res.status(200).send(SAMPLE); }); app.get("/search", async (_req, res) => { const search_term = _req.query.search_term?.toString(); let word_forms: WordForm[]; let lemmas: Lemma[]; if (!search_term) { lemmas = await Lemma.find({ relations: { word_forms: { lect: true } } }); } else { word_forms = await WordForm.find({ where: { word_form: Like(`%${search_term}%`) }, relations: { lemma: true }, }); let lemma_ids = word_forms.map((w) => w.lemma.lemma_name); lemmas = await Lemma.find({ where: { lemma_name: In(lemma_ids) }, relations: { word_forms: { lect: true } }, }); } res.status(200).send({ terms: lemmas.length, results: lemmas }); }); app.get("/lect", async (_req, res) => { const name = _req.query.name?.toString(); if (!name) { return void res.sendStatus(400); } const lect = await Lect.findOne({ where: { name: name }, relations: { word_forms: { lemma: true } }, }); res.status(200).send({ lect }); }); app.post("/lect", (_req, res) => { const lect_name = _req.query.lect_name?.toString(); if (!lect_name) { return void res.sendStatus(400); } const lect = new Lect(); lect.name = lect_name; lect.save().then((lect)=>{ res.status(200).send({ lect }) }); }); app.get("/lects", async (_req, res) => { const lects = await Lect.find(); res.status(200).send({ lects }); }); app.get("/lemma-detail", async (_req, res) => { const lemma_name = _req.query.lemma_name?.toString(); const lemma_detail = await Lemma.findOne({ where: { lemma_name: lemma_name }, relations: { word_forms: { lect: true }, examples: true, definitions: true, media: true, parts_of_speech: true } }); res.status(200).send({ lemma_detail }); }); app.post("/lemma", async (_req, res) => { const lect_name = _req.body.lect_name ?? null; const word_form_text = _req.body.word_form_text?.toString(); if(!word_form_text || /^\s*$/.test(word_form_text) || !lect_name){ console.error(`Error: ${JSON.stringify(_req.body)}`); return void res.status(400).send(); } let word_form = new WordForm(); let lemma = new Lemma(); let lect; try{ lect = await Lect.findOne({where:{name:lect_name}}); } catch { console.info(`Lect ${lect_name} not found.`); } if(!lect) { lect = new Lect(); lect.name = lect_name; await lect .save() .then((l)=>{console.info(`Created lect: ${JSON.stringify(l)}`)}); } lemma.lemma_name = word_form_text; word_form.word_form = word_form_text; word_form.lemma = lemma; word_form.lect = lect; lemma.word_forms = [word_form]; Lemma.save(lemma); let lemma_detail = await Lemma.findOne({ where: { lemma_name: word_form.lemma.lemma_name }, relations: { word_forms: { lect: true }, examples: true, definitions: true, media: true, parts_of_speech: true } }); res.status(200).send({lemma_detail}); }); app.get("/definition/:definition_id", async (_req, res) =>{ const definition_id:number = parseInt(_req.params.definition_id); if(!definition_id){ console.error(`Error: Could not find word form ${JSON.stringify({definition_id})}`); return void res.status(400).send(); } let definition = await Definition.findOne({where:{definition_id}}).then(); res.status(200).send({definition}); }); app.put("/definition", async (_req, res) => { const definition_text = _req.body.definition_text?.toString(); const definition_id = _req.body.definition_id ?? null; const lemma_name = _req.body.lemma_name?.toString(); if(definition_text == null || lemma_name == null){ console.error(`Error: ${JSON.stringify({definition_text:definition_text, lemma_name:lemma_name})}`); return void res.status(400).send(); } const lemma = await Lemma.findOne({where:{ lemma_name: lemma_name }}); if(!lemma){ return void res.status(400).send(); } let definition:Definition = new Definition(); definition.definition_text = definition_text; definition.lemma = lemma; if(definition_id){ definition.definition_id = definition_id; } Definition.save(definition) let lemma_detail = await Lemma.findOne({ where: { lemma_name: lemma_name }, relations: { word_forms: { lect: true }, examples: true, definitions: true, media: true, parts_of_speech: true } }); res.status(200).send({lemma_detail}); }); app.delete("/definition/:definition_id", async (_req, res) =>{ const definition_id:number = parseInt(_req.params.definition_id); if(!definition_id){ console.error(`Error: Could not find word form ${JSON.stringify({definition_id})}`); return void res.status(400).send(); } Definition.delete({definition_id: definition_id}); res.status(200).send(); }); /* examples */ app.get("/example/:example_id", async (_req, res) =>{ const example_id:number = parseInt(_req.params.example_id); if(!example_id){ console.error(`Error: Could not find word form ${JSON.stringify({example_id})}`); return void res.status(400).send(); } let example = await Example.findOne({where:{example_id}}).then(); res.status(200).send({definition: example}); }); app.put("/example", async (_req, res) => { const example_text = _req.body.example_text?.toString(); const example_id = _req.body.example_id ?? null; const lemma_name = _req.body.lemma_name?.toString(); if(!example_text || !lemma_name){ return void res.status(400).send(); } const lemma = await Lemma.findOne({where:{ lemma_name }}); if(!lemma){ return void res.status(400).send(); } var example:Example = new Example(); example.example_text = example_text; example.lemma = lemma; if(example_id){ example.example_id = example_id; } Example.save(example) var lemma_detail = await Lemma.findOne({ where: { lemma_name: lemma_name }, relations: { word_forms: { lect: true }, examples: true, definitions: true, media: true, parts_of_speech: true } }); res.status(200).send({lemma_detail}); }); app.delete("/example/:example_id", async (_req, res) =>{ const example_id:number = parseInt(_req.params.example_id); if(!example_id){ console.error(`Error: Could not find word form ${JSON.stringify({example_id:example_id})}`); return void res.status(400).send(); } Example.delete({example_id: example_id}); res.status(200).send(); }); /* word forms */ app.put("/word-form", async (_req, res) => { const word_form_text = _req.body.word_form_text?.toString(); const word_form_id = _req.body.word_form_id ?? null; const lemma_name = _req.body.lemma_name ?? null; if(!word_form_text || /^\s*$/.test(word_form_text) || !word_form_id){ console.error(`Error: ${JSON.stringify({word_form_text:word_form_text, word_form_id:word_form_id})}`); return void res.status(400).send(); } let word_form = await WordForm.findOne({where:{ word_form_id: word_form_id }, relations:{ lemma: true }}); if(!word_form){ if(lemma_name){ word_form = new WordForm(); let lemma = await Lemma.findOne({where:{lemma_name}}); } console.error(`Failed to find word form with ID ${word_form_id}`) return void res.status(400).send(); } word_form.word_form = word_form_text; WordForm.save(word_form); let lemma_detail = await Lemma.findOne({ where: { lemma_name: word_form.lemma.lemma_name }, relations: { word_forms: { lect: true }, examples: true, definitions: true, media: true, parts_of_speech: true } }); res.status(200).send({lemma_detail}); }); app.post("/word-form", async (_req, res) => { const lemma_name = _req.body.lemma_name ?? null; const lect_name = _req.body.lect_name ?? null; const word_form_text = _req.body.word_form_text?.toString(); if(!word_form_text || /^\s*$/.test(word_form_text) || !lect_name || !lemma_name){ console.error(`Error: ${JSON.stringify(_req.body)}`); return void res.status(400).send(); } let word_form = new WordForm(); let lemma = await Lemma.findOne({where:{lemma_name}}); let lect; try{ lect = await Lect.findOne({where:{name:lect_name}}); } catch { console.info(`Lect ${lect_name} not found.`); } if(!lemma){ console.error(`Error: ${JSON.stringify({lect:lect, lemma:lemma})}`); return void res.status(400).send(); } if(!lect) { lect = new Lect(); lect.name = lect_name; await lect .save() .then((l)=>{console.info(`Created lect: ${JSON.stringify(l)}`)}); } word_form.word_form = word_form_text; word_form.lemma = lemma; word_form.lect = lect; WordForm.save(word_form); let lemma_detail = await Lemma.findOne({ where: { lemma_name: word_form.lemma.lemma_name }, relations: { word_forms: { lect: true }, examples: true, definitions: true, media: true, parts_of_speech: true } }); res.status(200).send({lemma_detail}); }); app.delete("/word-form/:word_form_id", async (_req, res) =>{ const word_form_id:number = parseInt(_req.params.word_form_id); if(!word_form_id){ console.error(`Error: Could not find word form ${JSON.stringify({word_form_id:word_form_id})}`); return void res.status(400).send(); } WordForm.delete({word_form_id: word_form_id}); res.status(200).send(); }); app.listen(PORT, () => { console.log(`Backend started @ http://localhost:${PORT} !`); console.log(SAMPLE); }); } async function loadSheet() { const lect_repository = appDataSource.getRepository(Lect); const word_form_repository = appDataSource.getRepository(WordForm); const lemma_repository = appDataSource.getRepository(Lemma); await word_form_repository.clear(); await lemma_repository.clear(); await lect_repository.clear(); const rawData: string = fs.readFileSync(SOURCE_FILE, "utf8"); const rows: string[] = rawData.split("\n"); if (!rows || rows.length === 0) { console.error("No data found."); return; } const lect_names = rows.shift()?.split("\t"); const keys = rows.map((row) => row.split("\t")[0]?.split(";")[0]); console.log(keys); if (keys.length != rows.length) { console.error("Lemma count doesn't match number of rows."); return; } if (!lect_names) { console.error("No lects found"); return; } const lects = Array(); for (const lect of lect_names) { let l = new Lect(); l.name = lect; l.save(); lects.push(l); console.log(l); } const lemmas = Array(); for (let i = 0; i < rows.length; i++) { const row = rows[i]?.split("\t"); const lect_name = lect_names[i]; if (!lect_name) { console.error("No lect name"); break; } if (!row) { console.error("Row doesn't exist"); continue; } if (row.length !== lect_names.length) { console.error("Mismatched row size"); continue; } const lemma_key = row[0]; const lemma = new Lemma(); if (lemma_key == null || lemma_key.length == 0) { //assign a random UUID to the lemma as punishment for our failures lemma.lemma_name = crypto.randomUUID(); console.log( `womp womp, missing lemma name, calling it ${lemma.lemma_name}`, ); } else { lemma.lemma_name = lemma_key; } lemmas.push(lemma); lemma.word_forms = Array(); for (let j = 0; j < row.length; j++) { const cell = row?.[j]; const lect = lects[j]; if ( cell === null || cell === undefined || (typeof cell === "string" && cell.length === 0) || !lect ) { continue; } for (let word_form of cell.split(";")) { const f = new WordForm(); f.word_form = word_form; f.lect = lect; lemma.word_forms.push(f); } } } for (let i = 0; i < lemmas.length; i += 100) { await lemma_repository.save(lemmas.slice(i, i + 100)); console.log( `Saved ${Math.min(i + 100, lemmas.length)} / ${ lemmas.length } lemmas...`, ); } console.log(`Loaded ${lemmas.length} lemmas!`); }