Setup for postgres database & schema for blogposts.

This commit is contained in:
2021-01-03 18:16:52 +01:00
parent 5b9d9aeca8
commit 3aef0862ec
8 changed files with 374 additions and 0 deletions

23
api/database/index.js Normal file
View File

@@ -0,0 +1,23 @@
const configuration = require(`${__base}/config/configuration`).getInstance();
const PostgresDatabase = require(`${__base}/database/postgres`);
const user = configuration.get("database", "user");
const password = configuration.get("database", "password");
const host = configuration.get("database", "host");
const dbName = configuration.get("database", "database");
let postgresDB = new PostgresDatabase(user, password, host, dbName);
postgresDB.connect();
class Database {
};
Database.connect = () => postgresDB.connect();
Database.query = (sql, values) => postgresDB.query(sql, values);
Database.update = (sql, values) => postgresDB.update(sql, values);
Database.get = (sql, values) => postgresDB.get(sql, values);
Database.all = (sql, values) => postgresDB.all(sql, values);
module.exports = Database;

121
api/database/postgres.js Normal file
View File

@@ -0,0 +1,121 @@
const logger = require(`${__base}/logger`);
const fs = require('fs');
const path = require('path');
const { Pool } = require('pg');
class PostgresDatabase {
constructor(user, password='', host, database) {
this.user = user;
this.password = password;
this.host = host;
this.database = database;
this.pool = new Pool({
user,
password,
host,
database,
port: 5432
});
// save queries to postgres in local list
// should prevent this from overflowing.
}
connect() {
return this.pool.connect();
}
/**
* Run a SQL query against the database and retrieve one row.
* @param {String} sql SQL query
* @param {Array} values in the SQL query
* @returns {Promise}
*/
query(sql, values) {
const start = Date.now();
return this.pool.query(sql, values)
.then(res => {
const duration = Date.now() - start;
logger.debug("Executed query", {
sql,
values,
duration,
rows: res.rowCount
});
return res.rowCount;
})
.catch(err => {
console.log('query db error:', err);
// TODO log db error
throw err;
})
};
/**
* Update w/ query and return true or false if update was successful.
* @param {String} sql SQL query
* @param {Array} values in the SQL query
* @returns {Promise}
*/
update(sql, values) {
const start = Date.now();
return this.pool.query(sql, values)
.then(res => {
const duration = Date.now() - start;
logger.debug("Executed query", {
sql,
values,
duration,
rows: res.rowCount
});
if (res.rowCount > 0) {
return true;
}
return false;
})
.catch(err => {
console.log(err)
// TODO log db error
throw err;
})
}
/**
* Run a SQL query against the database and retrieve all the rows.
* @param {String} sql SQL query
* @param {Array} values in the SQL query
* @returns {Promise}
*/
all(sql, values) {
const start = Date.now();
return this.pool.query(sql, values)
.then(res => res.rows)
.catch(err => {
// TODO log db error
throw err;
})
}
/**
* Run a SQL query against the database and retrieve one row.
* @param {String} sql SQL query
* @param {Array} values in the SQL query
* @returns {Promise}
*/
get(sql, values) {
return this.pool.query(sql, values)
.then(res => res.rows[0])
.catch(err => {
// TODO log db error
throw err;
})
}
}
module.exports = PostgresDatabase;

View File

@@ -0,0 +1,7 @@
CREATE TABLE IF NOT EXISTS posts (
id serial PRIMARY KEY,
title text,
created timestamp DEFAULT CURRENT_TIMESTAMP,
updated timestamp DEFAULT CURRENT_TIMESTAMP,
markdown text
)

View File

@@ -0,0 +1,6 @@
CREATE TABLE IF NOT EXISTS seed (
seed_id serial PRIMARY KEY,
filename text,
run_date timestamp DEFAULT CURRENT_TIMESTAMP
);

View File

@@ -0,0 +1,90 @@
const path = require("path");
const fs = require("fs");
const fsPromises = require("fs/promises");
if (global.__base == undefined)
global.__base = path.join(__dirname, "../..");
const db = require("../index.js");
class SeedStep {
constructor(filepath) {
this.filepath = filepath;
this.filename = filepath.split("/").pop();
};
readData() {
this.data = JSON.parse(fs.readFileSync(this.filepath, 'utf-8'));
};
get isApplied() {
const query = `SELECT * FROM seed WHERE filename = $1`;
return db.query(query, [ this.filename ])
.then(resp => resp == 1 ? true : false)
}
commitStepToDb() {
const query = `INSERT INTO seed (filename) VALUES ($1)`;
return db.query(query, [ this.filename ]);
}
async applySeedData() {
if (await this.isApplied) {
console.log(`⚠️ Step: ${this.filename}, already applied.`);
return
}
console.log(`Seeding ${this.filename}:`);
const seedSteps = this.data.map(data => {
const { model, pk, fields } = data;
const columns = Object.keys(fields);
const values = Object.values(fields);
const parameterKeys = Array.from({length: values.length}, (v, k) => `$${k + 1}`);
const query = `INSERT INTO ${ model }
(${ columns.join(',') })
VALUES
(${ parameterKeys.join(',') })`;
return db.query(query, values)
});
const table = this.data[0].model;
return Promise.all(seedSteps)
.then(objects => console.log(`🌱 ${objects.length} object(s) applied to ${ table }.`))
.then(_ => this.commitStepToDb());
}
}
/**
* UTILS
*/
const readSeedFiles = () => {
const seedFolder = path.join(__base, "database/seeds/");
console.log(`Reading seeds from folder: ${seedFolder}\n`);
return fsPromises.readdir(seedFolder)
.then(files => files.map(filePath => {
const seedStep = new SeedStep(path.join(seedFolder, filePath));
seedStep.readData();
return seedStep;
}))
.catch(console.log)
};
const runAllSteps = (seedSteps) => {
return seedSteps.reduce(async (prevPromise, step) => {
await prevPromise;
return step.applySeedData();
}, Promise.resolve());
return Promise.all(promises);
}
/**
* Runner
*/
readSeedFiles()
.then(seedSteps => runAllSteps(seedSteps))
.finally(_ => process.exit(0));

View File

@@ -0,0 +1,69 @@
const path = require("path");
const fs = require("fs");
const fsPromises = require("fs/promises");
if (global.__base == undefined)
global.__base = path.join(__dirname, "../..");
const db = require("../index.js");
const posts = `posts.sql`;
const seed = `seed.sql`;
// TODO this is not used
const schemas = [
posts,
seed
];
const handleExit = (error=undefined) => {
if (error != undefined) {
console.log(`🚫 Exited with error: ${error}`);
process.exit(1);
}
console.log("✅ Exited setup successfully!");
process.exit(0);
};
const readSchemaFiles = () => {
const schemaFolder = path.join(__base, "database/schemas");
console.log("Reading schemas from folder:", schemaFolder);
return fsPromises.readdir(schemaFolder)
.then(files => files.map(filename => {
const filePath = path.join(schemaFolder, filename);
return fs.readFileSync(filePath, 'utf-8');
}))
}
const applyAll = schemas => {
schemas = schemas.reverse();
return schemas.reduce(async (prevPromise, schema) => {
const tableName = schema.split("CREATE TABLE IF NOT EXISTS ").pop().split(" (")[0];
console.log(`✏️ Applying schema: ${tableName}`);
await prevPromise;
return db.query(schema);
}, Promise.resolve());
}
/**
* Runner
*/
readSchemaFiles()
.then(schemas => applyAll(schemas))
.catch(err => handleExit(err))
.then(_ => process.exit(0))
// db.connect()
// .then(client => setup(client, schemas))
module.exports = db;

View File

@@ -0,0 +1,50 @@
const fs = require("fs");
const path = require("path");
if (global.__base == undefined)
global.__base = path.join(__dirname, "../..");
const db = require("../index.js");
const allTableNames = () => {
const sql = `
SELECT tablename
FROM pg_catalog.pg_tables
WHERE schemaname != 'pg_catalog' AND
schemaname != 'information_schema'
`;
return db.all(sql)
.then(rows => rows.map(row => row.tablename).reverse())
}
const teardown = (tableNames) => {
if (tableNames.length) {
console.log(`Tearing down tables:`)
console.log(` - ${tableNames.join("\n - ")}`)
const sql = `DROP TABLE IF EXISTS ${tableNames.join(",")}`;
return db.query(sql);
} else {
console.log("No tables left to drop.");
return Promise.resolve();
}
}
const handleExit = (error=undefined) => {
if (error != undefined) {
console.log(`🚫 Exited with error: ${error}`);
process.exit(1);
}
console.log("✅ Exited teardown successfully!");
process.exit(0);
};
db.connect()
.then(() => allTableNames())
.then(tableNames => teardown(tableNames))
.catch(console.log)
.finally(handleExit)
module.exports = db;

View File

@@ -0,0 +1,8 @@
[{
"model": "posts",
"pk": 12,
"fields": {
"title": "Making A Raspberry Pi Grafana Monitor",
"markdown": "# testost"
}
}]