From c5fa7ae1a6b46d8f7266229ca815bbd2dd644ce4 Mon Sep 17 00:00:00 2001 From: Adam Howard Date: Fri, 6 Sep 2013 16:54:50 +0100 Subject: [PATCH] Refactor the initial boot of Ghost, allowing Ghost updates to keep current configuration intact. Extracts all express-server-related code in index.js to core/server.js, leaving index.js purely for booting up Ghost's core components in a sensible order. Aside from the project's tidiness, this means that we can perform asynchronous configuration loading/checks before requiring any modules that read the config. --- .gitignore | 4 +- config.example.js | 102 +++++++++++++++ core/config-loader.js | 47 +++++++ core/ghost.js | 5 +- core/server.js | 285 ++++++++++++++++++++++++++++++++++++++++ index.js | 295 ++---------------------------------------- 6 files changed, 447 insertions(+), 291 deletions(-) create mode 100644 config.example.js create mode 100644 core/config-loader.js create mode 100644 core/server.js diff --git a/.gitignore b/.gitignore index 46acf420a1..c3499f13f7 100644 --- a/.gitignore +++ b/.gitignore @@ -43,4 +43,6 @@ projectFilesBackup CHANGELOG.md # Casper generated files -/core/test/functional/*.png \ No newline at end of file +/core/test/functional/*.png + +config.js diff --git a/config.example.js b/config.example.js new file mode 100644 index 0000000000..b2bbf0fdeb --- /dev/null +++ b/config.example.js @@ -0,0 +1,102 @@ +// # Ghost Configuration + +var path = require('path'), + config; + +// ## Environment +// **Warning:** Only change the settings below here if you are sure of what you are doing! +config = { + testing: { + database: { + client: 'sqlite3', + connection: { + filename: path.join(__dirname, '/core/server/data/ghost-test.db') + } + }, + server: { + host: '127.0.0.1', + port: '2369' + }, + // The url to use when providing links to the site; like RSS and email. + url: 'http://127.0.0.1:2369' + }, + + travis: { + database: { + client: 'sqlite3', + connection: { + filename: path.join(__dirname, '/core/server/data/ghost-travis.db') + } + }, + server: { + host: '127.0.0.1', + port: '2368' + }, + // The url to use when providing links to the site; like RSS and email. + url: 'http://127.0.0.1:2368' + }, + + // Default configuration + development: { + database: { + client: 'sqlite3', + connection: { + filename: path.join(__dirname, '/core/server/data/ghost-dev.db') + }, + debug: false + }, + server: { + host: '127.0.0.1', + port: '2368' + }, + // The url to use when providing links to the site; like RSS and email. + url: 'http://127.0.0.1:2368', + // Example mail config + mail: { + transport: 'sendgrid', + host: 'smtp.sendgrid.net', + options: { + service: 'Sendgrid', + auth: { + user: '', // Super secret username + pass: '' // Super secret password + } + } + } + }, + + staging: { + database: { + client: 'sqlite3', + connection: { + filename: path.join(__dirname, '/core/server/data/ghost-staging.db') + }, + debug: false + }, + server: { + host: '127.0.0.1', + port: '2368' + }, + // The url to use when providing links to the site; like RSS and email. + url: 'http://127.0.0.1:2368' + }, + + production: { + database: { + client: 'sqlite3', + connection: { + filename: path.join(__dirname, '/core/server/data/ghost.db') + }, + debug: false + }, + server: { + host: '127.0.0.1', + port: '2368' + }, + // The url to use when providing links to the site; like RSS and email. + url: 'http://127.0.0.1:2368' + } +}; + +// Export config +module.exports = config; \ No newline at end of file diff --git a/core/config-loader.js b/core/config-loader.js new file mode 100644 index 0000000000..ff9e8ec167 --- /dev/null +++ b/core/config-loader.js @@ -0,0 +1,47 @@ +var fs = require('fs'), + when = require('when'); + +function writeConfigFile() { + var written = when.defer(); + + /* Check for config file and copy from config.example.js + if one doesn't exist. After that, start the server. */ + fs.exists('config.example.js', function checkTemplate(templateExists) { + var read, + write; + + if (!templateExists) { + throw new Error('Could not locate a configuration file. Please check your deployment for config.js or config.example.js.'); + } + + // Copy config.example.js => config.js + read = fs.createReadStream('config.example.js'); + read.on('error', function (err) { + throw new Error('Could not open config.example.js for read.'); + }); + read.on('end', written.resolve); + + write = fs.createWriteStream('config.js'); + write.on('error', function (err) { + throw new Error('Could not open config.js for write.'); + }); + + read.pipe(write); + }); + + return written.promise; +} + +exports.loadConfig = function () { + var loaded = when.defer(); + /* Check for config file and copy from config.example.js + if one doesn't exist. After that, start the server. */ + fs.exists('config.js', function checkConfig(configExists) { + if (configExists) { + loaded.resolve(); + } else { + writeConfigFile().then(loaded.resolve).otherwise(loaded.reject); + } + }); + return loaded.promise; +}; diff --git a/core/ghost.js b/core/ghost.js index 0e312416dd..d820f13e82 100644 --- a/core/ghost.js +++ b/core/ghost.js @@ -61,8 +61,7 @@ statuses = { * @constructor */ Ghost = function () { - var app, - polyglot; + var polyglot; if (!instance) { instance = this; @@ -88,11 +87,9 @@ Ghost = function () { // Holds the dbhash (mainly used for cookie secret) instance.dbHash = undefined; - app = express(); polyglot = new Polyglot(); _.extend(instance, { - app: function () { return app; }, config: function () { return config[process.env.NODE_ENV]; }, // there's no management here to be sure this has loaded diff --git a/core/server.js b/core/server.js new file mode 100644 index 0000000000..6795d5a9eb --- /dev/null +++ b/core/server.js @@ -0,0 +1,285 @@ +// Module dependencies +var express = require('express'), + when = require('when'), + _ = require('underscore'), + colors = require("colors"), + semver = require("semver"), + errors = require('./server/errorHandling'), + admin = require('./server/controllers/admin'), + frontend = require('./server/controllers/frontend'), + api = require('./server/api'), + Ghost = require('./ghost'), + I18n = require('./shared/lang/i18n'), + helpers = require('./server/helpers'), + packageInfo = require('../package.json'), + +// Variables + loading = when.defer(), + server = express(), + ghost = new Ghost(); + +// ##Custom Middleware + +// ### Auth Middleware +// Authenticate a request by redirecting to login if not logged in. +// We strip /ghost/ out of the redirect parameter for neatness +function auth(req, res, next) { + if (!req.session.user) { + var path = req.path.replace(/^\/ghost\/?/gi, ''), + redirect = '', + msg; + + if (path !== '') { + msg = { + type: 'error', + message: 'Please Sign In', + status: 'passive', + id: 'failedauth' + }; + // let's only add the notification once + if (!_.contains(_.pluck(ghost.notifications, 'id'), 'failedauth')) { + ghost.notifications.push(msg); + } + redirect = '?r=' + encodeURIComponent(path); + } + return res.redirect('/ghost/signin/' + redirect); + } + + next(); +} + + +// Check if we're logged in, and if so, redirect people back to dashboard +// Login and signup forms in particular +function redirectToDashboard(req, res, next) { + if (req.session.user) { + return res.redirect('/ghost/'); + } + + next(); +} + +function redirectToSignup(req, res, next) { + api.users.browse().then(function (users) { + if (users.length === 0) { + return res.redirect('/ghost/signup/'); + } + }); + + next(); +} + +// While we're here, let's clean up on aisle 5 +// That being ghost.notifications, and let's remove the passives from there +// plus the local messages, as they have already been added at this point +// otherwise they'd appear one too many times +function cleanNotifications(req, res, next) { + ghost.notifications = _.reject(ghost.notifications, function (notification) { + return notification.status === 'passive'; + }); + next(); +} + +// ## AuthApi Middleware +// Authenticate a request to the API by responding with a 401 and json error details +function authAPI(req, res, next) { + if (!req.session.user) { + // TODO: standardize error format/codes/messages + res.json(401, { error: 'Please sign in' }); + return; + } + + next(); +} + +// ### GhostAdmin Middleware +// Uses the URL to detect whether this response should be an admin response +// This is used to ensure the right content is served, and is not for security purposes +function isGhostAdmin(req, res, next) { + res.isAdmin = /(^\/ghost$|^\/ghost\/)/.test(req.url); + + next(); +} + +// ### GhostLocals Middleware +// Expose the standard locals that every external page should have available, +// separating between the frontend / theme and the admin +function ghostLocals(req, res, next) { + // Make sure we have a locals value. + res.locals = res.locals || {}; + res.locals.version = packageInfo.version; + + if (res.isAdmin) { + api.users.read({id: req.session.user}).then(function (currentUser) { + _.extend(res.locals, { + // pass the admin flash messages, settings and paths + messages: ghost.notifications, + settings: ghost.settings(), + availableThemes: ghost.paths().availableThemes, + availablePlugins: ghost.paths().availablePlugins, + currentUser: { + name: currentUser.attributes.full_name, + profile: currentUser.attributes.profile_picture + } + }); + next(); + }).otherwise(function () { + _.extend(res.locals, { + // pass the admin flash messages, settings and paths + messages: ghost.notifications, + settings: ghost.settings(), + availableThemes: ghost.paths().availableThemes, + availablePlugins: ghost.paths().availablePlugins + }); + next(); + }); + } else { + next(); + } +} + +// ### DisableCachedResult Middleware +// Disable any caching until it can be done properly +function disableCachedResult(req, res, next) { + res.set({ + "Cache-Control": "no-cache, must-revalidate", + "Expires": "Sat, 26 Jul 1997 05:00:00 GMT" + }); + + next(); +} + +// Expose the promise we will resolve after our pre-loading +ghost.loaded = loading.promise; + +when.all([ghost.init(), helpers.loadCoreHelpers(ghost)]).then(function () { + + // ##Configuration + server.configure(function () { + server.use(isGhostAdmin); + server.use(express.favicon(__dirname + '/core/shared/favicon.ico')); + server.use(I18n.load(ghost)); + server.use(express.bodyParser({})); + server.use(express.bodyParser({uploadDir: __dirname + '/content/images'})); + server.use(express.cookieParser(ghost.dbHash)); + server.use(express.cookieSession({ cookie: { maxAge: 60000000 }})); + server.use(ghost.initTheme(server)); + if (process.env.NODE_ENV !== "development") { + server.use(express.logger()); + server.use(express.errorHandler({ dumpExceptions: false, showStack: false })); + } + }); + + // Development only configuration + server.configure("development", function () { + server.use(express.errorHandler({ dumpExceptions: true, showStack: true })); + server.use(express.logger('dev')); + }); + + // post init config + server.use(ghostLocals); + // So on every request we actually clean out reduntant passive notifications from the server side + server.use(cleanNotifications); + + // ## Routing + + // ### API routes + /* TODO: auth should be public auth not user auth */ + // #### Posts + server.get('/api/v0.1/posts', authAPI, disableCachedResult, api.requestHandler(api.posts.browse)); + server.post('/api/v0.1/posts', authAPI, disableCachedResult, api.requestHandler(api.posts.add)); + server.get('/api/v0.1/posts/:id', authAPI, disableCachedResult, api.requestHandler(api.posts.read)); + server.put('/api/v0.1/posts/:id', authAPI, disableCachedResult, api.requestHandler(api.posts.edit)); + server.del('/api/v0.1/posts/:id', authAPI, disableCachedResult, api.requestHandler(api.posts.destroy)); + // #### Settings + server.get('/api/v0.1/settings', authAPI, disableCachedResult, api.cachedSettingsRequestHandler(api.settings.browse)); + server.get('/api/v0.1/settings/:key', authAPI, disableCachedResult, api.cachedSettingsRequestHandler(api.settings.read)); + server.put('/api/v0.1/settings', authAPI, disableCachedResult, api.cachedSettingsRequestHandler(api.settings.edit)); + // #### Themes + server.get('/api/v0.1/themes', authAPI, disableCachedResult, api.requestHandler(api.themes.browse)); + // #### Users + server.get('/api/v0.1/users', authAPI, disableCachedResult, api.requestHandler(api.users.browse)); + server.get('/api/v0.1/users/:id', authAPI, disableCachedResult, api.requestHandler(api.users.read)); + server.put('/api/v0.1/users/:id', authAPI, disableCachedResult, api.requestHandler(api.users.edit)); + // #### Tags + server.get('/api/v0.1/tags', authAPI, disableCachedResult, api.requestHandler(api.tags.all)); + // #### Notifications + server.del('/api/v0.1/notifications/:id', authAPI, disableCachedResult, api.requestHandler(api.notifications.destroy)); + server.post('/api/v0.1/notifications/', authAPI, disableCachedResult, api.requestHandler(api.notifications.add)); + + + // ### Admin routes + /* TODO: put these somewhere in admin */ + server.get(/^\/logout\/?$/, function redirect(req, res) { + res.redirect(301, '/signout/'); + }); + server.get(/^\/signout\/?$/, admin.logout); + server.get('/ghost/login/', function redirect(req, res) { + res.redirect(301, '/ghost/signin/'); + }); + server.get('/ghost/signin/', redirectToSignup, redirectToDashboard, admin.login); + server.get('/ghost/signup/', redirectToDashboard, admin.signup); + server.get('/ghost/forgotten/', redirectToDashboard, admin.forgotten); + server.post('/ghost/forgotten/', admin.resetPassword); + server.post('/ghost/signin/', admin.auth); + server.post('/ghost/signup/', admin.doRegister); + server.post('/ghost/changepw/', auth, admin.changepw); + server.get('/ghost/editor/:id', auth, admin.editor); + server.get('/ghost/editor', auth, admin.editor); + server.get('/ghost/content', auth, admin.content); + server.get('/ghost/settings*', auth, admin.settings); + server.get('/ghost/debug/', auth, admin.debug.index); + server.get('/ghost/debug/db/export/', auth, admin.debug['export']); + server.post('/ghost/debug/db/import/', auth, admin.debug['import']); + server.get('/ghost/debug/db/reset/', auth, admin.debug.reset); + server.post('/ghost/upload', admin.uploader); + server.get(/^\/(ghost$|(ghost-admin|admin|wp-admin|dashboard|signin)\/?)/, auth, function (req, res) { + res.redirect('/ghost/'); + }); + server.get('/ghost/', redirectToSignup, auth, admin.index); + + // ### Frontend routes + /* TODO: dynamic routing, homepage generator, filters ETC ETC */ + server.get('/rss/', frontend.rss); + server.get('/rss/:page/', frontend.rss); + server.get('/:slug', frontend.single); + server.get('/', frontend.homepage); + server.get('/page/:page/', frontend.homepage); + + + // ## Start Ghost App + server.listen( + ghost.config().server.port, + ghost.config().server.host, + function () { + + // Tell users if their node version is not supported, and exit + if (!semver.satisfies(process.versions.node, packageInfo.engines.node)) { + console.log( + "\n !!! INVALID NODE VERSION !!!\n".red, + "Ghost requires node version".red, + packageInfo.engines.node.yellow, + "as defined in package.json\n".red + ); + + process.exit(-1); + } + + // Alpha warning, reminds users this is not production-ready software (yet) + // Remove once software becomes suitably 'ready' + console.log( + "\n !!! ALPHA SOFTWARE WARNING !!!\n".red, + "Ghost is in the early stages of development.\n".red, + "Expect to see bugs and other issues (but please report them.)\n".red + ); + + // Startup message + console.log("Express server listening on address:", + ghost.config().server.host + ':' + + ghost.config().server.port); + + // Let everyone know we have finished loading + loading.resolve(); + } + ); +}, errors.logAndThrowError); diff --git a/index.js b/index.js index 2741c0e175..53f4f31df9 100644 --- a/index.js +++ b/index.js @@ -1,290 +1,13 @@ -// # Ghost main app file -// Contains the app configuration and all of the routing +// # Ghost bootloader +// Orchestrates the loading of Ghost + +var configLoader = require('./core/config-loader.js'), + error = require('./core/server/errorHandling'); // If no env is set, default to development process.env.NODE_ENV = process.env.NODE_ENV || 'development'; -// Module dependencies -var express = require('express'), - when = require('when'), - _ = require('underscore'), - colors = require("colors"), - semver = require("semver"), - errors = require('./core/server/errorHandling'), - admin = require('./core/server/controllers/admin'), - frontend = require('./core/server/controllers/frontend'), - api = require('./core/server/api'), - Ghost = require('./core/ghost'), - I18n = require('./core/shared/lang/i18n'), - helpers = require('./core/server/helpers'), - packageInfo = require('./package.json'), - -// Variables - loading = when.defer(), - ghost = new Ghost(); - -// ##Custom Middleware - -// ### Auth Middleware -// Authenticate a request by redirecting to login if not logged in. -// We strip /ghost/ out of the redirect parameter for neatness -function auth(req, res, next) { - if (!req.session.user) { - var path = req.path.replace(/^\/ghost\/?/gi, ''), - redirect = '', - msg; - - if (path !== '') { - msg = { - type: 'error', - message: 'Please Sign In', - status: 'passive', - id: 'failedauth' - }; - // let's only add the notification once - if (!_.contains(_.pluck(ghost.notifications, 'id'), 'failedauth')) { - ghost.notifications.push(msg); - } - redirect = '?r=' + encodeURIComponent(path); - } - return res.redirect('/ghost/signin/' + redirect); - } - - next(); -} - - -// Check if we're logged in, and if so, redirect people back to content -// Login and signup forms in particular -function redirectToIndex(req, res, next) { - if (req.session.user) { - return res.redirect('/ghost/'); - } - - next(); -} - -function redirectToSignup(req, res, next) { - api.users.browse().then(function (users) { - if (users.length === 0) { - return res.redirect('/ghost/signup/'); - } - }); - - next(); -} - -// While we're here, let's clean up on aisle 5 -// That being ghost.notifications, and let's remove the passives from there -// plus the local messages, as they have already been added at this point -// otherwise they'd appear one too many times -function cleanNotifications(req, res, next) { - ghost.notifications = _.reject(ghost.notifications, function (notification) { - return notification.status === 'passive'; - }); - next(); -} - -// ## AuthApi Middleware -// Authenticate a request to the API by responding with a 401 and json error details -function authAPI(req, res, next) { - if (!req.session.user) { - // TODO: standardize error format/codes/messages - res.json(401, { error: 'Please sign in' }); - return; - } - - next(); -} - -// ### GhostAdmin Middleware -// Uses the URL to detect whether this response should be an admin response -// This is used to ensure the right content is served, and is not for security purposes -function isGhostAdmin(req, res, next) { - res.isAdmin = /(^\/ghost$|^\/ghost\/)/.test(req.url); - - next(); -} - -// ### GhostLocals Middleware -// Expose the standard locals that every external page should have available, -// separating between the frontend / theme and the admin -function ghostLocals(req, res, next) { - // Make sure we have a locals value. - res.locals = res.locals || {}; - res.locals.version = packageInfo.version; - - if (res.isAdmin) { - api.users.read({id: req.session.user}).then(function (currentUser) { - _.extend(res.locals, { - // pass the admin flash messages, settings and paths - messages: ghost.notifications, - settings: ghost.settings(), - availableThemes: ghost.paths().availableThemes, - availablePlugins: ghost.paths().availablePlugins, - currentUser: { - name: currentUser.attributes.full_name, - profile: currentUser.attributes.profile_picture - } - }); - next(); - }).otherwise(function () { - _.extend(res.locals, { - // pass the admin flash messages, settings and paths - messages: ghost.notifications, - settings: ghost.settings(), - availableThemes: ghost.paths().availableThemes, - availablePlugins: ghost.paths().availablePlugins - }); - next(); - }); - } else { - next(); - } -} - -// ### DisableCachedResult Middleware -// Disable any caching until it can be done properly -function disableCachedResult(req, res, next) { - res.set({ - "Cache-Control": "no-cache, must-revalidate", - "Expires": "Sat, 26 Jul 1997 05:00:00 GMT" - }); - - next(); -} - -// Expose the promise we will resolve after our pre-loading -ghost.loaded = loading.promise; - -when.all([ghost.init(), helpers.loadCoreHelpers(ghost)]).then(function () { - - // ##Configuration - ghost.app().configure(function () { - ghost.app().use(isGhostAdmin); - ghost.app().use(express.favicon(__dirname + '/core/shared/favicon.ico')); - ghost.app().use(I18n.load(ghost)); - ghost.app().use(express.bodyParser({})); - ghost.app().use(express.bodyParser({uploadDir: __dirname + '/content/images'})); - ghost.app().use(express.cookieParser(ghost.dbHash)); - ghost.app().use(express.cookieSession({ cookie: { maxAge: 60000000 }})); - ghost.app().use(ghost.initTheme(ghost.app())); - if (process.env.NODE_ENV !== "development") { - ghost.app().use(express.logger()); - ghost.app().use(express.errorHandler({ dumpExceptions: false, showStack: false })); - } - }); - - // Development only configuration - ghost.app().configure("development", function () { - ghost.app().use(express.errorHandler({ dumpExceptions: true, showStack: true })); - ghost.app().use(express.logger('dev')); - }); - - // post init config - ghost.app().use(ghostLocals); - // So on every request we actually clean out reduntant passive notifications from the server side - ghost.app().use(cleanNotifications); - - // ## Routing - - // ### API routes - /* TODO: auth should be public auth not user auth */ - // #### Posts - ghost.app().get('/api/v0.1/posts', authAPI, disableCachedResult, api.requestHandler(api.posts.browse)); - ghost.app().post('/api/v0.1/posts', authAPI, disableCachedResult, api.requestHandler(api.posts.add)); - ghost.app().get('/api/v0.1/posts/:id', authAPI, disableCachedResult, api.requestHandler(api.posts.read)); - ghost.app().put('/api/v0.1/posts/:id', authAPI, disableCachedResult, api.requestHandler(api.posts.edit)); - ghost.app().del('/api/v0.1/posts/:id', authAPI, disableCachedResult, api.requestHandler(api.posts.destroy)); - // #### Settings - ghost.app().get('/api/v0.1/settings', authAPI, disableCachedResult, api.cachedSettingsRequestHandler(api.settings.browse)); - ghost.app().get('/api/v0.1/settings/:key', authAPI, disableCachedResult, api.cachedSettingsRequestHandler(api.settings.read)); - ghost.app().put('/api/v0.1/settings', authAPI, disableCachedResult, api.cachedSettingsRequestHandler(api.settings.edit)); - // #### Themes - ghost.app().get('/api/v0.1/themes', authAPI, disableCachedResult, api.requestHandler(api.themes.browse)); - // #### Users - ghost.app().get('/api/v0.1/users', authAPI, disableCachedResult, api.requestHandler(api.users.browse)); - ghost.app().get('/api/v0.1/users/:id', authAPI, disableCachedResult, api.requestHandler(api.users.read)); - ghost.app().put('/api/v0.1/users/:id', authAPI, disableCachedResult, api.requestHandler(api.users.edit)); - // #### Tags - ghost.app().get('/api/v0.1/tags', authAPI, disableCachedResult, api.requestHandler(api.tags.all)); - // #### Notifications - ghost.app().del('/api/v0.1/notifications/:id', authAPI, disableCachedResult, api.requestHandler(api.notifications.destroy)); - ghost.app().post('/api/v0.1/notifications/', authAPI, disableCachedResult, api.requestHandler(api.notifications.add)); - - - // ### Admin routes - /* TODO: put these somewhere in admin */ - ghost.app().get(/^\/logout\/?$/, function redirect(req, res) { - res.redirect(301, '/signout/'); - }); - ghost.app().get(/^\/signout\/?$/, admin.logout); - ghost.app().get('/ghost/login/', function redirect(req, res) { - res.redirect(301, '/ghost/signin/'); - }); - ghost.app().get('/ghost/signin/', redirectToSignup, redirectToIndex, admin.login); - ghost.app().get('/ghost/signup/', redirectToIndex, admin.signup); - ghost.app().get('/ghost/forgotten/', redirectToIndex, admin.forgotten); - ghost.app().post('/ghost/forgotten/', admin.resetPassword); - ghost.app().post('/ghost/signin/', admin.auth); - ghost.app().post('/ghost/signup/', admin.doRegister); - ghost.app().post('/ghost/changepw/', auth, admin.changepw); - ghost.app().get('/ghost/editor/:id', auth, admin.editor); - ghost.app().get('/ghost/editor', auth, admin.editor); - ghost.app().get('/ghost/content', auth, admin.content); - ghost.app().get('/ghost/settings*', auth, admin.settings); - ghost.app().get('/ghost/debug/', auth, admin.debug.index); - ghost.app().get('/ghost/debug/db/export/', auth, admin.debug['export']); - ghost.app().post('/ghost/debug/db/import/', auth, admin.debug['import']); - ghost.app().get('/ghost/debug/db/reset/', auth, admin.debug.reset); - ghost.app().post('/ghost/upload', admin.uploader); - ghost.app().get(/^\/(ghost$|(ghost-admin|admin|wp-admin|dashboard|signin)\/?)/, auth, function (req, res) { - res.redirect('/ghost/'); - }); - ghost.app().get('/ghost/', redirectToSignup, auth, admin.index); - - // ### Frontend routes - /* TODO: dynamic routing, homepage generator, filters ETC ETC */ - ghost.app().get('/rss/', frontend.rss); - ghost.app().get('/rss/:page/', frontend.rss); - ghost.app().get('/:slug', frontend.single); - ghost.app().get('/', frontend.homepage); - ghost.app().get('/page/:page/', frontend.homepage); - - - // ## Start Ghost App - ghost.app().listen( - ghost.config().server.port, - ghost.config().server.host, - function () { - - // Tell users if their node version is not supported, and exit - if (!semver.satisfies(process.versions.node, packageInfo.engines.node)) { - console.log( - "\n !!! INVALID NODE VERSION !!!\n".red, - "Ghost requires node version".red, - packageInfo.engines.node.yellow, - "as defined in package.json\n".red - ); - - process.exit(-1); - } - - // Alpha warning, reminds users this is not production-ready software (yet) - // Remove once software becomes suitably 'ready' - console.log( - "\n !!! ALPHA SOFTWARE WARNING !!!\n".red, - "Ghost is in the early stages of development.\n".red, - "Expect to see bugs and other issues (but please report them.)\n".red - ); - - // Startup message - console.log("Express server listening on address:", - ghost.config().server.host + ':' - + ghost.config().server.port); - - // Let everyone know we have finished loading - loading.resolve(); - } - ); -}, errors.logAndThrowError); +configLoader.loadConfig().then(function () { + // The server and its dependencies require a populated config + require('./core/server'); +}).otherwise(error.logAndThrowError);