crypto = require('crypto'), extend = require('soak').mixin, inherit = require('soak').inherit, metrics = require('./metrics'), EventEmitter = require('events').EventEmitter; module.exports = { // Create a base observable object that inherits from EventEmitter. This // allows us to create new objects that can trigger events very easily. // // Example: // // var Store = Observable.extend({ // save: function () {} // }); Observable: inherit(EventEmitter), // Helper function for creating an index file in a directory. It loads all // modules within the provided directory excluding the file provided as // "indexpath". These will then be returned. // // Example: // // module.exports = utils.index(__dirname, __filename); index: function (dirname, indexpath) { var extensions = {js: 1}, modules = {}; indexpath = indexpath || path.join(dirname, 'index.js'); // Load all exported objects into a single module.
var HTTPError = exports.HTTPError = inherit(Error, { constructor: function HTTPError(status, message) { Error.call(this, message); metrics.increment('error.' + status); // Remove the Error itself from the stack trace. // http://code.google.com/p/v8/wiki/JavaScriptStackTraceApi Error.captureStackTrace(this, this.constructor); this.name = this.constructor.name; this.status = status; this.message = message || ''; }, // Renders the error in string format suitable for use in a HTTP response. toString: function () { return this.status + ' ' + this.name + ':\n\n' + this.message; }, // Renders the error in html format suitable for use in an HTTP response. toHTMLString: function () { return [ '<h1>' + this.status + ' ' + this.name + '</h1>', '<p>' + this.message + '</p>' ].join('\n'); } }, { // Provides a clean method for creating a new extension (sub class) of this // object. // // Example: // // var NotFound = HTTPError.extend({}); extend: inherit.constructor().extend });
var fs = require('fs'), path = require('path'), existsSync = fs.existsSync || path.existsSync, inherit = require('soak').inherit, config = require('../config.default.json'), EventEmitter = require('events').EventEmitter; function deepExtend(target, object) { Object.getOwnPropertyNames(object).forEach(function (key) { var value = object[key]; if (typeof value === "object" && value !== null && !Array.isArray(value)) { target[key] = deepExtend(target[key] || {}, value); } else { target[key] = value; } }); return target; } module.exports = inherit(EventEmitter, { constructor: function Config(localconfig){ if(localconfig && existsSync(localconfig)){ return deepExtend(config, require(localconfig)); }else{ return config; } } })
module.exports = inherit(Object, { defaults: null, constructor: function SQLite(options) { this.defaults = {html: '', css: '', javascript: ''}; this.database = options.location; }, connect: function (fn) { var self = this; this.connection = new sqlite3.Database(this.database, function () { fs.readFile(__dirname + '/../../build/full-db-v3.sqlite.sql', 'utf8', function (err, sql) { if (err) { return fn(err); } self.connection.serialize(function () { sql = sql.trim(); if (sql) { self.connection.exec(sql, fn); } else { fn(); } }); }); }); }, disconnect: function (fn) { this.connection.close(); fn(); }, getBin: function (params, fn) { var values = [params.id, params.revision], _this = this; this.connection.get(templates.getBin, values, function (err, result) { if (err) { return fn(err); } if (result) { result = _this.convertBinDates(result); } fn(null, result && _this.applyBinDefaults(result)); }); }, setBin: function (params, fn) { var now = new Date(), values = [ params.javascript || '', params.css || '', params.html || '', now, now, params.url, params.revision, params.streamingKey, params.settings ], sql = templates.setBin; this.connection.run(sql, values, function (err) { if (err || !this.changes) { return fn(err); } fn(null, this.lastID); }); }, setBinOwner: function (params, fn) { var sql = templates.setBinOwner, values = [params.name, params.url, params.revision, new Date(), params.summary, params.html, params.css, params.javascript]; // TODO: Re-factor common callbacks into helpers. this.connection.run(sql, values, function (err) { if (err || !this.changes) { return fn(err); } fn(null, this.lastID); }); }, setBinPanel: function (panel, params, fn) { var values = [ params[panel], params.settings, new Date(), params.url, params.revision, params.streamingKey ], allowed = {html: 1, css: 1, javascript: 1}, sql = templates.setBinPanel.replace(':panel', panel); if (allowed[panel]) { this.connection.run(sql, values, function (err) { if (err || !this.changes) { return fn(err || 'no-entry'); } fn(null, this.lastID); }); } else { fn('invalid-panel'); } }, getLatestBin: function (params, fn) { var values = [params.id], sql = templates.getLatestBin, _this = this; this.connection.get(sql, values, function (err, result) { if (err) { return fn(err); } if (result) { result = _this.convertBinDates(result); } fn(null, result && _this.applyBinDefaults(result)); }); }, getLatestBinForUser: function (id, fn) { var sql = templates.getLatestBinForUser, query = this.connection.get.bind(this.connection), _this = this; query(sql, [id], function (err, result) { var sql = templates.getBinByUrlAndRevision; if (err) { return fn(err); } if (typeof result === 'undefined') { return fn(null, null); } _this.getBin({ id: result.url, revision: result.revision }, fn); }); }, getBinsByUser: function (id, fn) { var sql = templates.getBinsByUser, _this = this; this.connection.all(sql, [id], function (err, results) { if (err) { return fn(err); } var sql = templates.getBinByUrlAndRevision, collected = []; // i.e. if they've never saved anything before results.forEach(function (result) { collected.push(_this.applyBinDefaults(result)); }); fn(null, collected); }); }, // Get all bins from the owners field getAllOwners: function (fn) { // Get all the 'owned' bins this.connection.run(templates.getAllOwners, [], fn); }, getOwnersBlock: function (start, size, fn) { // Get all the 'owned' bins this.connection.run(templates.getOwnersBlock, [start, size], fn); }, generateBinId: function (fn, attempts) { var id = shortcode(), mysql = this; attempts = attempts || 1; if (attempts <= 10) { this.connection.get(templates.binExists, [id], function (err, result) { if (err) { fn(err); } else if (result) { attempts += 1; mysql.generateBinId(fn, attempts); } else { fn(null, id); } }); } else { fn(new Error("too-many-tries")); } }, getUser: function (id, fn) { var _this = this; this.connection.get(templates.getUser, [id], function (err, result) { if (err) { return fn(err); } if (!result) { _this.connection.get(templates.getUserByEmail, [id], function (err, result) { if (err) { return fn(err); } if (result) { result = _this.convertUserDates(result); } fn(null, result); }); } else { if (result) { result = _this.convertUserDates(result); } fn(null, result); } }); }, getUserByEmail: function (email, fn) { var _this = this; this.connection.get(templates.getUserByEmail, [email], function (err, result) { if (err) { return fn(err); } if (result) { result = _this.convertUserDates(result); } fn(null, result); }); }, setUser: function (params, fn) { var now = new Date(), values = [ params.name, params.key, params.email, now, now, now ], sql = templates.setUser; this.connection.run(sql, values, function (err) { if (err) { return fn(err); } fn(null, this.lastID); }); }, touchOwners: function (params, fn) { // params.date is only for use when populating the summary field var values = [params.date || new Date(), params.name, params.url, params.revision]; this.connection.run(templates.touchOwners, values, function (err) { if (err) { return fn(err); } if (typeof fn === 'function') { fn(null); } }); }, updateOwners: function (params, fn) { // params.date is only for use when populating the summary field var values = [params.date || new Date(), params.summary, params.panel_open, params.name, params.url, params.revision]; var panel = params.panel, allowed = {html: 1, css: 1, javascript: 1}, sql = templates.updateOwners.replace(':panel', panel); if (allowed[panel]) { this.connection.run(sql, values, function (err) { if (err) { return fn(err); } if (typeof fn === 'function') { fn(null); } }); } else { fn('invalid-panel'); } }, populateOwners: function (params, fn) { // params.date is only for use when populating the summary field var values = [params.date || new Date(), params.summary, params.html, params.css, params.javascript, params.name, params.url, params.revision]; this.connection.run(templates.populateOwners, values, function (err, result) { if (err) { return fn(err); } if (typeof fn === 'function') { fn(null); } }); }, touchLogin: function (id, fn) { var now = new Date(); this.connection.run(templates.touchLogin, [now, id], function (err) { if (err) { return fn(err); } fn(null); }); }, updateUserEmail: function (id, email, fn) { var now = new Date(); this.connection.run(templates.updateUserEmail, [email, now, id], function (err) { if (err) { return fn(err); } fn(null); }); }, updateUserKey: function (id, key, fn) { var now = new Date(); this.connection.run(templates.updateUserKey, [key, now, id], function (err) { if (err) { return fn(err); } fn(null); }); }, // Different to updateUserKey() in that it also sets the created timestamp // which is required to differentiate between a JSBin 2 user and a new // one. upgradeUserKey: function (id, key, fn) { var now = new Date(); this.connection.run(templates.upgradeUserKey, [key, now, now, id], function (err) { if (err) { return fn(err); } fn(null); }); }, getUserByForgotToken: function (token, fn) { var sql = templates.getUserForForgotToken, _this = this; this.connection.get(sql, [token, new Date()], function (err, result) { if (err) { return fn(err); } if (result) { result = _this.convertUserDates(result); } fn(null, result); }); }, setForgotToken: function (user, token, fn) { var sql = templates.setForgotToken, expires = this.expireDate(), params = [user, token, expires, new Date()]; this.connection.run(sql, params, function (err) { if (err) { return fn(err); } fn(); }); }, expireForgotToken: function (token, fn) { var sql = templates.deleteExpiredForgotToken; // Allow all old tokens to be expired with same call. if (typeof token === 'function') { fn = token; token = null; } this.connection.run(sql, [new Date(), token, null], function (err, results) { fn(err || null); }); }, expireForgotTokenByUser: function (user, fn) { var sql = templates.deleteExpiredForgotToken; this.connection.run(sql, [new Date(), null, user], function (err) { fn(err || null); }); }, expireDate: function () { var expires = new Date(); expires.setUTCDate(expires.getUTCDate() + 1); return expires; }, applyBinDefaults: function (bin) { for (var prop in this.defaults) { if (bin[prop] == null) { // Using == to catch null and undefined. bin[prop] = this.defaults[prop]; } } this.convertDates(bin, 'last_updated'); bin.active = bin.active === 'y'; if (!bin.last_updated || isNaN(bin.last_updated.getTime())) bin.last_updated = new Date('2012-07-23 00:00:00'); try { bin.settings = JSON.parse(bin.settings || '{}'); } catch (e) { // this is likely because the settings were screwed in a beta build bin.settings = {}; } return bin; }, convertUserDates: function (user) { return this.convertDates(user, 'created', 'updated', 'last_login'); }, convertBinDates: function (bin) { return this.convertDates(bin, 'created', 'last_viewed'); }, convertDates: function (obj/* keys */) { var keys = [].slice.call(arguments, 1); keys.forEach(function (key) { if (obj && obj[key]) { var date = new Date(); date.setTime(obj[key]); obj[key] = date; } }); return obj; }, reportBin: function (params, fn) { var now = new Date(), values = [ now, params.url, params.revision ], sql = templates.reportBin; this.connection.run(sql, values, function (err) { if (err) { return fn(err); } fn(null); }); }, archiveBin: function (bin, fn) { var values = [bin.archive, bin.name, bin.url, bin.revision], sql = templates.archiveBin; this.connection.run(sql, values, function (err, result) { if (err || !this.changes) { return fn(err); } fn(null, results); }); } });