Skip to content

Instantly share code, notes, and snippets.

@timmywil
Last active August 29, 2015 14:01
Show Gist options
  • Save timmywil/b0ab17daa4f2d52e62af to your computer and use it in GitHub Desktop.
Save timmywil/b0ab17daa4f2d52e62af to your computer and use it in GitHub Desktop.
Connect-based postgres session storage for the latest pg module
/**
* Connect-based Postgres session storage
* Based off https://github.com/brianc/postgres-session
*/
'use strict';
var assert = require('assert');
var pg = require('pg');
var Store = require('express-session').Store;
/**
* Postgres session storage
* @constructor PostgresStore
* @param {Object} options
* @param {string} options.url Database URL connection string
*/
function PostgresStore(options) {
Store.call(this, options);
assert(options && options.url, 'A database URL must be specified');
this.url = options.url;
}
PostgresStore.prototype = Object.create(Store.prototype);
/**
* Create a callback for queries
* @private
* @param {Function} done pg done function
* @param {Function} callback Our completion callback
*/
function release(done, callback) {
return function(err, result) {
done();
callback.call(this, err, result);
};
}
/**
* Connect to the postgres store
* @param {string} query
* @param {Array} data
* @param {Function} callback
*/
PostgresStore.prototype.query = function connect(query, data, callback) {
pg.connect(this.url, function(err, client, done) {
callback = release(done, callback);
if (err) {
callback(err);
return;
}
client.query(query, data, callback);
});
};
/**
* Retrieve the number of sessions
* @member {Function} length
* @memberof PostgresStore
* @instance
* @param {Function} callback
*/
PostgresStore.prototype.length = function length(callback) {
this.query('select count(*) from sessions', [], callback);
};
/**
* Clear the table of all sessions
* @member {Function} clear
* @memberof PostgresStore
* @instance
* @param {Function} callback
*/
PostgresStore.prototype.clear = function clear(callback) {
this.query('truncate table sessions', [], callback);
};
/**
* Set session data
* @memberof PostgresStore
* @instance
* @param {string} hash Session identifier
* @param {string} data Session data
* @param {Function} callback
*/
PostgresStore.prototype.set = function set(hash, data, callback) {
this.query(
'select sessionstore($1, $2)',
[hash, JSON.stringify(data)],
callback
);
};
/**
* Retrieve a session
* @memberof PostgresStore
* @instance
* @param {string} hash Session identifier
* @param {Function} callback
*/
PostgresStore.prototype.get = function get(hash, callback) {
this.query(
'select data from sessions where key = $1',
[hash],
function(err, result) {
if (err) {
callback(err);
return;
}
var row;
// Parse data to JSON
result = result && result.rows && (row = result.rows[0]) && row.data && JSON.parse(row.data);
callback(null, result || null);
}
);
};
/**
* Destroy a session
* @memberof PostgresStore
* @instance
* @param {string} hash Session identifier
* @param {Function} callback
*/
PostgresStore.prototype.destroy = function destroy(hash, callback) {
this.query(
'delete from sessions where key = $1',
[hash],
callback
);
};
/* Exports
---------------------------------------------------------------------- */
module.exports = PostgresStore;
'use strict';
var assert = require('assert');
var PostgresStore = require('../lib/PostgresStore');
var config = require('../tasks/env').app.middleware.session.module.arguments[1];
var uuid = require('../public/js/models/uuid');
var sha = uuid();
describe('sessions', function() {
var store = new PostgresStore(config);
it('set a new session', function(done) {
store.set(sha, { sid: 'awesome' }, function(err, result) {
assert.equal(result.rowCount, 1);
done();
});
});
it('gets the created session', function(done) {
store.get(sha, function(err, result) {
if (err) return done(err);
assert(result.sid);
done();
});
});
it('does not find a session not present', function(done) {
store.get('1', function(err, result) {
assert.equal(result, null);
done();
});
});
it('returns the number of sessions', function(done) {
store.length(function(err, result) {
if (err) return done(err);
assert(result.rows[0].count > 0);
done();
});
});
it('deletes the new session', function(done) {
store.destroy(sha, function(err) {
if (err) return done(err);
store.get(sha, function(err, result) {
assert.equal(result, null, 'Session destroyed');
done();
});
});
});
it('clears all sessions', function(done) {
store.clear(function(err, result) {
if (err) return done(err);
assert.equal(result.rows.length, 0);
done();
});
});
});
describe('sessions - fail', function() {
// Unable to connect
var store = new PostgresStore({ url: config.url.replace('quickcue', 'quick') });
it('does not connect to set a new session', function(done) {
store.set(sha, { sid: 'awesome' }, function(err) {
assert(err);
done();
});
});
it('does not connect to get the created session', function(done) {
store.get(sha, function(err) {
assert(err);
done();
});
});
it('does not connect to return the number of sessions', function(done) {
store.length(function(err) {
assert(err);
done();
});
});
it('does not connect to delete the new session', function(done) {
store.destroy(sha, function(err) {
assert(err);
done();
});
});
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment