diff --git a/.gitignore b/.gitignore index 6286b53..c9712e2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,14 +1,14 @@ -/node_modules -/bower_components -/coverage-reporter.html -/npm-debug.log - -/.vagrant -/package.box - -/.idea *~ .DS_Store -/config.coffee +.idea/ +.vagrant/ +node_modules/ +bower_components/ + +/package.box +/npm-debug.log + /session.key +/config.coffee +/coverage-reporter.html diff --git a/INSTALL.md b/INSTALL.md index 2414464..53dfdc3 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -18,10 +18,6 @@ server_name rp.rpvhost.net; - location ~ /\.git { - deny all; - } - location / { proxy_set_header X-Real-IP $remote_addr; proxy_pass http://unix:/home/rpadmin/rootpanel.sock:/; diff --git a/app.coffee b/app.coffee index bbcc7e9..ba8fad3 100755 --- a/app.coffee +++ b/app.coffee @@ -1,157 +1,6 @@ #!/usr/bin/env coffee -{EventEmitter} = require 'events' +Root = require './core' -global.rp = global.app = module.exports = new EventEmitter() - -app.libs = - _: require 'underscore' - Q: require 'q' - fs: require 'fs' - path: require 'path' - jade: require 'jade' - async: require 'async' - crypto: require 'crypto' - moment: require 'moment-timezone' - request: require 'request' - express: require 'express' - child_process: require 'child_process' - -cookieParser = require 'cookie-parser' -BunyanMongo = require 'bunyan-mongo' -bodyParser = require 'body-parser' -nodemailer = require 'nodemailer' -Insight = require 'insight' -morgan = require 'morgan' -Mabolo = require 'mabolo' -bunyan = require 'bunyan' - -harp = require 'harp' - -{_, fs, path, express} = app.libs - -if fs.existsSync "#{__dirname}/config.coffee" - config = require './config' -else - config = require './sample/core.config.coffee' - -app.package = require './package' -utils = require './core/utils' - -if fs.existsSync config.web.listen - fs.unlinkSync config.web.listen - -insight = new Insight - # 这个代码用于向 RootPanel 开发者提交匿名的统计信息 - # This code used to send anonymous usage metrics to RootPanel developers - # 您不必修改这里 You do not have to modify it - trackingCode: 'UA-49193300-7' - packageName: app.package.name - packageVersion: app.package.version - -insight.track 'app.coffee' - -mailer = nodemailer.createTransport config.email.account - -mabolo = new Mabolo utils.mongodbUri _.extend config.mongodb, - name: config.mongodb.test - -bunyanMongo = new BunyanMongo() - -mabolo.connect().done (db) -> - bunyanMongo.setDB db - -logger = bunyan.createLogger - name: app.package.name - streams: [ - type: 'raw' - level: 'info' - stream: bunyanMongo - , - level: process.env.LOG_LEVEL ? 'debug' - stream: process.stdout - ] - -PlanManager = require './core/PlanManager' -NodeManager = require './core/NodeManager' -CacheManager = require './core/CacheManager' -TranslationManager = require './core/TranslationManager' -PluginManager = require './core/extends/PluginManager' -HookManager = require './core/extends/HookManager' -ComponentManager = require './core/extends/ComponentManager' - -_.extend app, - plans: new PlanManager config.plans - nodes: new NodeManager config.nodes - cache: new CacheManager() - hooks: new HookManager() - - components: new ComponentManager() - translations: new TranslationManager config.i18n - - utils: utils - config: config - mabolo: mabolo - mailer: mailer - logger: logger - models: mabolo.models - insight: insight - express: express() - -require './core/model/Account' -require './core/model/Financials' -require './core/model/CouponCode' -require './core/model/Notification' -require './core/model/SecurityLog' -require './core/model/Ticket' -require './core/model/Component' - -PaymentProviderManager = require './core/extends/PaymentProviderManager' -CouponProviderManager = require './core/extends/CouponProviderManager' -NotificationManager = require './core/extends/NotificationManager' - -app.extends = - notifications: new NotificationManager() - payments: new PaymentProviderManager() - coupons: new CouponProviderManager() - -app.middleware = require './core/middleware' - -app.getHooks = app.extends.hook.getHooks -app.applyHooks = app.extends.hook.applyHooks - -app.express.use bodyParser.json() -app.express.use cookieParser() - -app.express.use app.middleware.reqHelpers -app.express.use app.middleware.session() -app.express.use app.middleware.logger() -app.express.use app.middleware.csrf() -app.express.use app.middleware.authenticate -app.express.use app.middleware.renderHelpers - -app.express.set 'views', path.join __dirname, 'core/view' -app.express.set 'view engine', 'jade' - -app.express.use '/component', require './core/router/component' -app.express.use '/account', require './core/router/account' -app.express.use '/ticket', require './core/router/ticket' -app.express.use '/admin', require './core/router/admin' -app.express.use '/panel', require './core/router/panel' - -app.plugins = new PluginManager() - -app.express.use '/bower_components', express.static './bower_components' -app.express.use harp.mount './core/static' - -app.express.get '/', (req, res) -> - res.redirect '/panel/' - -exports.start = _.once -> - app.express.listen config.web.listen, -> - app.started = true - app.logger.info "RootPanel start at #{config.web.listen}" - app.emit 'app.started' - -unless module.parent - exports.start() +Root.findConfig(__dirname).done (config) -> + global.root = new Root config diff --git a/core/CacheManager.coffee b/core/cache.coffee similarity index 92% rename from core/CacheManager.coffee rename to core/cache.coffee index b1e9132..1f57861 100644 --- a/core/CacheManager.coffee +++ b/core/cache.coffee @@ -4,7 +4,11 @@ redis = require 'redis' _ = require 'underscore' Q = require 'q' -class CacheManager +### + Public: Cache utils + You can access a global instance via `root.cache`. +### +class Cache constructor: ({host, port, password}) -> @redis = redis.createClient port, host, auth_pass: password diff --git a/core/index.coffee b/core/index.coffee new file mode 100644 index 0000000..bbbbc34 --- /dev/null +++ b/core/index.coffee @@ -0,0 +1 @@ +module.exports = require './root' diff --git a/core/middleware.coffee b/core/middleware.coffee index 63588f6..f5b5a88 100644 --- a/core/middleware.coffee +++ b/core/middleware.coffee @@ -63,7 +63,7 @@ exports.reqHelpers = (req, res, next) -> res.createCookie = (name, value) -> res.cookie name, value, - expires: new Date(Date.now() + config.account.cookie_time) + expires: new Date(Date.now() + config.web.cookie_time) res.createToken = (account = req.account) -> account.createToken('full_access', req.getClientInfo()).then (token) -> diff --git a/core/root.coffee b/core/root.coffee new file mode 100644 index 0000000..bc3cb4d --- /dev/null +++ b/core/root.coffee @@ -0,0 +1,211 @@ +{EventEmitter} = require 'events' +cookieParser = require 'cookie-parser' +bodyParser = require 'body-parser' +nodemailer = require 'nodemailer' +Insight = require 'insight' +express = require 'express' +Mabolo = require 'mabolo' +morgan = require 'morgan' +path = require 'path' +harp = require 'harp' +fs = require 'q-io/fs' +_ = require 'lodash' + +### + Class: Root object for control RootPanel, An instance is always available as the `root` global. +### +module.exports = class Root extends EventEmitter + ### + Public: Find and load configure file. + + * `rootPath` {String} e.g. `/home/rpadmin/RootPanel` + + Return {String}. + ### + @findConfig: (rootPath) -> + configPath = path.resolve rootPath, '../config.coffee' + defaultPath = path.resolve rootPath, '../sample/config.coffee' + + fs.exists(configPath).then (exists) -> + fs.read if exists then configPath else defaultPath + + # Public: Config object + config: null + # Public: Node package object + package: null + # Public: express application + express: null + # Public: nodemailer instance + mailer: null + # Public: {Mabolo} instance + mabolo: null + # Public: {Insight} instance + insight: null + # Public: global {Cache} instance + cache: null + + # Public: {Account} Model + Account: null + # Public: {Financials} Model + Financials: null + # Public: {CouponCode} Model + CouponCode: null + # Public: {Notification} Model + Notification: null + # Public: {SecurityLog} Model + SecurityLog: null + # Public: {Ticket} Model + Ticket: null + # Public: {Component} Model + Component: null + + # Public: global {HookRegistry} instance + hooks: null + # Public: global {ViewRegistry} instance + views: null + # Public: global {WidgetRegistry} instance + widgets: null + # Public: global {ComponentRegistry} instance + components: null + # Public: global {CouponTypeRegistry} instance + couponTypes: null + # Public: global {PaymentProviderRegistry} instance + paymentProviders: null + + # Public: global {I18nManager} instance + i18n: null + # Public: global {PluginManager} instance + plugins: null + # Public: global {ServerManager} instance + servers: null + # Public: global {BillingManager} instance + billing: null + # Public: global {NotificationManager} instance + notifications: null + + ### + Public: Construct a RootPanel instance. + + * `config` {Object} Configure object + * `package` {Object} Node package object + + ### + constructor: (@config, @package) -> + @package ?= require '../package' + + Cache = require './cache' + + HookRegistry = require './registry/hook' + ViewRegistry = require './registry/view' + WidgetRegistry = require './registry/widget' + ComponentRegistry = require './registry/component' + CouponTypeRegistry = require './registry/coupon-type' + PaymentProviderRegistry = require './registry/payment-provider' + + I18nManager = require './i18n-manager' + PluginManager = require './plugin-manager' + ServerManager = require './server-manager' + BillingManager = require './billing-manager' + NotificationManager = require './notification-manager' + + _.extend @, + express: express() + mailer: nodemailer.createTransport @config.email.account + mabolo: new Mabolo mongodbUri @config.mongodb + + insight: new Insight + trackingCode: TRACKING_CODE + pkg: @package + + cache: new Cache() + + Account: require './model/account' + Financials: require './model/financials' + CouponCode: require './model/coupon-code' + Notification: require './model/notification' + SecurityLog: require './model/security-log' + Ticket: require './model/ticket' + Component: require './model/component' + + hooks: new HookRegistry() + views: new ViewRegistry() + widgets: new WidgetRegistry() + components: new ComponentRegistry() + couponTypes: new CouponTypeRegistry() + paymentProviders: new PaymentProviderRegistry() + + i18n: new I18nManager @config.i18n + plugins: new PluginManager @config.plugins + servers: new ServerManager @config.server + billing: new BillingManager @config.billing + notifications: new NotificationManager() + + @express.use bodyParser.json() + @express.use cookieParser() + @express.use morgan 'combined' + + middleware = require './middleware' + + @express.use middleware.reqHelpers + @express.use middleware.session() + @express.use middleware.csrf() + @express.use middleware.authenticate + @express.use middleware.renderHelpers + + @express.use '/admin', require './router/admin' + @express.use '/panel', require './router/panel' + @express.use '/ticket', require './router/ticket' + @express.use '/account', require './router/account' + @express.use '/component', require './router/component' + + @express.use '/bower_components', express.static @resolve '../bower_components' + @express.use harp.mount @resolve 'static' + + @express.get '/', (req, res) -> + res.redirect '/panel/' + + ### + Public: Run RootPanel and start web service. + + Return a {Promise}. + ### + start: -> + @trackUsage 'root.start' + + fs.exists(@config.web.listen).then (exists) -> + fs.unlink(@config.web.listen) if exists + .then -> + Q.Promise (resolve, reject) => + @express.listen @config.web.listen, (err) => + return reject err if err + + @emit 'started' + resolve() + + ### + Public: Resolve path based on core directory. + + * `arguments...` {String} paths + + return {String}. + ### + resolve: -> + return path.resolve __dirname, arguments... + + ### + Public: Send usage metrics to Google Analytics. + + * `path` {String} e.g. `root.start` + + ### + trackUsage: (path) -> + @insight.track path.split('.')... + +# Private: This code used to send anonymous usage metrics to RootPanel developers. +TRACKING_CODE = 'UA-49193300-7' + +mongodbUri = ({user, password, host, name}) -> + if user and password + return "mongodb://#{user}:#{password}@#{host}/#{name}" + else + return "mongodb://#{host}/#{name}" diff --git a/core/utils.coffee b/core/utils.coffee index 98bc362..f54bee5 100644 --- a/core/utils.coffee +++ b/core/utils.coffee @@ -1,14 +1,18 @@ +validator = require 'validator' crypto = require 'crypto' -_ = require 'underscore' +_ = require 'lodash' exports.rx = - username: /^[a-z][0-9a-z_]{2,23}$/ - email: /^\w+([-+.]\w+)*@\w+([-+.]\w+)*$/ - password: /^.+$/ domain: /^(\*\.)?[A-Za-z0-9]+(\-[A-Za-z0-9]+)*(\.[A-Za-z0-9]+(\-[A-Za-z0-9]+)*)*$/ filename: /^[A-Za-z0-9_\-\.]+$/ url: /^https?:\/\/[^\s;]*$/ +validator.extend 'isUsername', (username) -> + return /^[a-z][0-9a-z_]{2,23}$/.test username + +validator.extend 'isPassword', (password) -> + return /^.+$/.test password + exports.sha256 = (data) -> if data return crypto.createHash('sha256').update(data).digest('hex') @@ -48,19 +52,3 @@ exports.pickErrorName = (error) -> return "#{err.path}_exist" return err.message - -exports.formatBillingTrigger = (name, plugin_name) -> - [part1, part2] = name.split '.' - - if part2 - return name - else - return "#{plugin_name}.#{part1}" - -exports.mongodbUri = (config) -> - {user, password, host, name} = config - - if user and password - return "mongodb://#{user}:#{password}@#{host}/#{name}" - else - return "mongodb://#{host}/#{name}" diff --git a/docs/design-zh.md b/docs/design-zh.md index f5d31dc..7a0e41d 100644 --- a/docs/design-zh.md +++ b/docs/design-zh.md @@ -54,5 +54,6 @@ RootPanel 使用 Mabolo 管理数据模型。 ## 应用入口 * Routers `/core/router`: 路由 -* App Entry `/app`: RootPanel 主程序的入口 +* Root `/core/root` 全局对象 `root` +* App Entry `/app`: 入口点 * Tests `/core/test`: 自动测试 diff --git a/package.json b/package.json index bfc1c94..ec773cb 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ }, "dependencies": { "async": "^0.9.0", + "async-q": "^0.2.2", "body-parser": "^1.9.3", "bower": "^1.3.12", "bunyan": "^1.2.3", @@ -37,8 +38,8 @@ "express-session": "^1.9.2", "get-parameter-names": "^0.2.0", "harp": "^0.14.0", - "insight": "^0.4.3", - "jade": "^1.7.0", + "insight": "^0.5.3", + "jade": "^1.9.2", "json-stable-stringify": "^1.0.0", "lodash": "^3.6.0", "mabolo": "^0.3.0-rc.1", @@ -50,11 +51,13 @@ "negotiator": "^0.4.9", "nodemailer": "^1.3.0", "q": "^1.2.0", + "q-io": "^1.12.0", "redis": "^0.12.1", "request": "^2.48.0", "semver": "^4.1.0", "ssh2": "^0.3.6", - "underscore": "^1.7.0" + "underscore": "^1.7.0", + "validator": "^3.37.0" }, "devDependencies": { "chai": "^1.10.0",