diff --git a/.gitignore b/.gitignore index 5b012ec..2cc4b77 100644 --- a/.gitignore +++ b/.gitignore @@ -57,4 +57,8 @@ Temporary Items public/generated/* # Don't commit environment file -.env \ No newline at end of file +.env + +# Don't commit database migration config file or dev database +knexfile.js +dev.sqlite3 \ No newline at end of file diff --git a/app/migration_helpers.js b/app/migration_helpers.js new file mode 100644 index 0000000..c940ccd --- /dev/null +++ b/app/migration_helpers.js @@ -0,0 +1,54 @@ +'use strict'; + +/** + * Migration helper methods + * @type {Object} + */ +module.exports = { + + get_db_type(knex) { + switch(knex.client.config.client) { + case 'postgresql': + case 'postgres': + case 'pg': + return 'pg'; + + default: + return knex.client.config.client; + } + }, + + pg_timestamp_update_function () { + return `CREATE OR REPLACE FUNCTION progblog_update_modified_column() + RETURNS TRIGGER AS $$ + BEGIN + NEW.updated_at = now(); + RETURN NEW; + END; + $$ language 'plpgsql';`; + }, + + pg_create_timestamp_update_trigger(table) { + return `CREATE TRIGGER update_${table}_modtime + BEFORE UPDATE ON "${table}" + FOR EACH ROW + EXECUTE PROCEDURE progblog_update_modified_column();`; + }, + + pg_delete_timestamp_update_trigger(table) { + return `DROP TRIGGER IF EXISTS update_${table}_modtime ON "${table}";`; + }, + + sqlite3_create_timestamp_update_trigger(table) { + return `CREATE TRIGGER IF NOT EXISTS [UpdateModified] + BEFORE UPDATE ON "${table}" + FOR EACH ROW + BEGIN + UPDATE "${table}" SET updated_at = CURRENT_TIMESTAMP WHERE id = old.id; + END`; + }, + + sqlite3_delete_timestamp_update_trigger() { + return `DROP TRIGGER IF EXISTS [UpdateModified]`; + } +}; \ No newline at end of file diff --git a/migrations/20160224105517_initial.js b/migrations/20160224105517_initial.js new file mode 100644 index 0000000..279e411 --- /dev/null +++ b/migrations/20160224105517_initial.js @@ -0,0 +1,74 @@ +'use strict'; + +const helpers = require('../app/migration_helpers'); + +exports.up = function(knex, Promise) { + const dbType = helpers.get_db_type(knex); + + // Defer has the promise resulting from creating the initial tables + let defer = knex.schema.createTableIfNotExists('users', (table) => { + table.comment('User authentication table'); + table.increments() + .unsigned(); + table.string('user'); + table.binary('password_hash'); + table.timestamp('created_at') + .defaultTo(knex.fn.now()); + table.timestamp('updated_at') + .defaultTo(knex.fn.now()); + }).then(() => { + return knex.schema.createTableIfNotExists('posts', (table) => { + table.comment('Blog post table'); + table.increments() + .unsigned(); + table.integer('created_by') + .unsigned() + .references('users.id'); + table.string('uri') + .index() + .comment('The uri segment of the blog post'); + table.string('title') + .comment('The title of the blog post'); + table.text('content') + .comment('The content of the blog post'); + table.timestamp('created_at') + .defaultTo(knex.fn.now()); + table.timestamp('updated_at') + .defaultTo(knex.fn.now()); + }); + }); + + if ('pg' === dbType) { + // Create the function to update + return knex.schema.raw(helpers.pg_timestamp_update_function()) + // Create the tables + .then(() => defer) + + // Add trigger for tables + .then(() => knex.schema.raw(helpers.pg_create_timestamp_update_trigger('users'))) + .then(() => knex.schema.raw(helpers.pg_create_timestamp_update_trigger('posts'))); + } else if ('sqlite3' === dbType) { + return defer.then(() => knex.schema.raw(helpers.sqlite3_create_timestamp_update_trigger('users'))) + .then(() => knex.schema.raw(helpers.sqlite3_create_timestamp_update_trigger('posts'))); + } else { + return defer; + } +}; + +exports.down = function(knex, Promise) { + const dbType = helpers.get_db_type(knex); + let defer = knex.schema.dropTableIfExists('posts') + .then(() => knex.schema.dropTableIfExists('users')); + + if ('pg' === dbType) { + return knex.schema.raw(helpers.pg_delete_timestamp_update_trigger('users')) + .then(() => knex.schema.raw(helpers.pg_delete_timestamp_update_trigger('posts'))) + .then(() => defer); + } else if ('sqlite3' === dbType) { + return knex.schema.raw(helpers.sqlite3_delete_timestamp_update_trigger('users')) + .then(() => knex.schema.raw(helpers.sqlite3_delete_timestamp_update_trigger('posts'))) + .then(() => defer); + } else { + return defer; + } +}; diff --git a/package.json b/package.json index 52de28b..17bdf33 100644 --- a/package.json +++ b/package.json @@ -17,13 +17,16 @@ "glob": "^6.0.4", "helmet": "^1.1.0", "highlight.js": "^9.1.0", + "knex": "^0.10.0", "lodash": "^4.5.0", "marked": "^0.3.5", "morgan": "~1.6.1", "nodemon": "^1.9.0", + "scrypt": "^6.0.1", + "sqlite3": "^3.1.1", "winston": "^2.1.1" }, - "description": "An Opinionated Take on express with use of ES6 features", + "description": "A simple blog with built-in code snippet functionality", "devDependencies": { "chai": "^3.4.1", "chai-as-promised": "^5.2.0", @@ -42,9 +45,6 @@ "pre-commit": "^1.1.2", "supertest": "^1.1.0" }, - "directories": { - "lib": "./lib" - }, "engines": { "node": ">4.0.0" }, @@ -63,7 +63,7 @@ }, "repository": { "type": "git", - "url": "https://github.com/timw4mail/crispy-train.git" + "url": "https://git.timshomepage.net/timw4mail/ProgBlog.git" }, "scripts": { "start": "nodemon server.js",