diff --git a/.gitignore b/.gitignore index a39c0ba..1008d33 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ node_modules/ public/ test/ -data/ \ No newline at end of file +data/ +words.txt \ No newline at end of file diff --git a/src/db/index.ts b/src/db/index.ts new file mode 100644 index 0000000..9c4df56 --- /dev/null +++ b/src/db/index.ts @@ -0,0 +1,79 @@ +import { logError, logSuccess, projectRoot } from "../utils"; +import fs from "fs"; +import path from "path"; +import { Client, QueryResult } from "pg"; + +export const tableExists = async (db: Client): Promise => { + const result = await db + .query( + "SELECT EXISTS (SELECT * FROM information_schema.tables WHERE table_name = 'words');" + ) + .catch((err) => err); + + return result.rows[0].exists; +}; + +// used for generating param query string +// expand(rowCount: 3, columnCount: 2) returns "($1, $2), ($3, $4), ($5, $6)" +function expand(rowCount: number, columnCount = 1, startAt = 1) { + var index = startAt; + return Array(rowCount) + .fill(0) + .map( + (v) => + `(${Array(columnCount) + .fill(0) + .map((v) => `$${index++}`) + .join(", ")})` + ) + .join(", "); +} + +export const populateDB = async (db: Client) => { + let filename = "words.txt"; + let filepath = path.join(projectRoot, filename); + + // check for source file + if (!fs.existsSync(filepath)) { + logError( + "Could not find `okiba/words.txt` for populating the database!\n" + ); + process.exit(1); + } + + await db + .query( + "CREATE TABLE words (id SERIAL PRIMARY KEY, val VARCHAR NOT NULL, taken BOOLEAN DEFAULT 'f');" + ) + .catch((err) => err); + + fs.readFile( + filepath, + { encoding: "utf-8" }, + async (err: Error | null, data: string) => { + if (err != null) { + logError(err.message); + throw err; + } + + let arr: Array = data.split("\r\n"); + + // 1k entries per query + const chunkSize = 1000; + for (let i = 0; i < arr.length; i += chunkSize) { + const chunk: Array = arr.slice(i, i + chunkSize); + + const queryStr = { + text: `INSERT INTO words (val) VALUES ${expand( + chunk.length + )}`, + values: chunk, + }; + + await db.query(queryStr); + } + } + ); + + logSuccess("Successfully populated the database!"); +}; diff --git a/src/main.ts b/src/main.ts index 96e3c0c..6700a43 100644 --- a/src/main.ts +++ b/src/main.ts @@ -3,14 +3,15 @@ import dotenv from "dotenv"; import BinRouter from "./routes/bin"; import { Client, Pool } from "pg"; import { createDataDir, errorHandler, logError, logSuccess } from "./utils"; +import { tableExists, populateDB } from "./db"; const main = async () => { dotenv.config(); createDataDir(); // init db - const pool = await new Pool(); - const client = await new Client(); + const pool = new Pool(); + const client = new Client(); client .connect() @@ -22,6 +23,12 @@ const main = async () => { throw err; }); + // check if `words` table exists + // else populate db with `/okiba/words.txt` + if (!(await tableExists(client))) { + await populateDB(client); + } + // start server const app: Express = express(); const port = process.env.PORT; diff --git a/src/routes/bin.ts b/src/routes/bin.ts index 2ad8bab..06361ae 100644 --- a/src/routes/bin.ts +++ b/src/routes/bin.ts @@ -18,7 +18,7 @@ export default function BinRouter(db: Pool) { const word: Word | undefined = await getAvailableWord(db); if (word != undefined) { - await fs.writeFile( + fs.writeFile( path.join(projectRoot, "data", word.val + ".txt"), body, (err) => err diff --git a/src/utils/index.ts b/src/utils/index.ts index 4a86448..80668f6 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -6,6 +6,7 @@ import fs from "fs"; // logs export const logError = (msg: string) => console.error(chalk.bold.red(msg)); export const logSuccess = (msg: string) => console.log(chalk.bold.green(msg)); +export const logWarning = (msg: string) => console.log(chalk.bold.yellow(msg)); export const projectRoot = path.join(__dirname, "..", "..");