mirror of
https://github.com/HackPlan/RootPanel.git
synced 2026-03-26 07:44:10 +08:00
refactor with promise
This commit is contained in:
27
app.coffee
27
app.coffee
@@ -6,6 +6,7 @@ global.app = module.exports = new EventEmitter()
|
||||
|
||||
app.libs =
|
||||
_: require 'underscore'
|
||||
Q: require 'q'
|
||||
fs: require 'fs'
|
||||
path: require 'path'
|
||||
jade: require 'jade'
|
||||
@@ -29,10 +30,11 @@ harp = require 'harp'
|
||||
|
||||
{_, fs, path, express} = app.libs
|
||||
|
||||
if fs.existsSync "#{__dirname}/config.coffee"
|
||||
config = require './config'
|
||||
else
|
||||
config = require './sample/core.config.coffee'
|
||||
unless global.config
|
||||
if fs.existsSync "#{__dirname}/config.coffee"
|
||||
config = require './config'
|
||||
else
|
||||
config = require './sample/core.config.coffee'
|
||||
|
||||
app.package = require './package'
|
||||
utils = require './core/utils'
|
||||
@@ -60,7 +62,9 @@ mabolo = new Mabolo utils.mongodbUri _.extend config.mongodb,
|
||||
|
||||
bunyanMongo = new BunyanMongo()
|
||||
|
||||
mabolo.on 'connected', bunyanMongo.setDB.bind bunyanMongo
|
||||
mabolo.connect().then (db) ->
|
||||
bunyanMongo.setDB db
|
||||
.catch console.error
|
||||
|
||||
logger = bunyan.createLogger
|
||||
name: app.package.name
|
||||
@@ -102,21 +106,19 @@ require './core/model/Ticket'
|
||||
require './core/model/Component'
|
||||
|
||||
app.extends = require './core/extends'
|
||||
app.templates = require './core/templates'
|
||||
app.clusters = require './core/clusters'
|
||||
app.billing = require './core/billing'
|
||||
app.middleware = require './core/middleware'
|
||||
app.notification = require './core/notification'
|
||||
|
||||
app.applyHooks = ->
|
||||
app.extends.hook.applyHooks.apply null, arguments
|
||||
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.errorHandling
|
||||
app.express.use app.middleware.csrf()
|
||||
app.express.use app.middleware.authenticate
|
||||
app.express.use app.middleware.accountHelpers
|
||||
@@ -126,9 +128,7 @@ 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 '/billing', require './core/router/billing'
|
||||
app.express.use '/ticket', require './core/router/ticket'
|
||||
app.express.use '/coupon', require './core/router/coupon'
|
||||
app.express.use '/admin', require './core/router/admin'
|
||||
app.express.use '/panel', require './core/router/panel'
|
||||
|
||||
@@ -149,9 +149,6 @@ app.express.get '/', (req, res) ->
|
||||
|
||||
exports.start = _.once ->
|
||||
app.express.listen config.web.listen, ->
|
||||
if fs.existsSync config.web.listen
|
||||
fs.chmodSync config.web.listen, 0o770
|
||||
|
||||
app.started = true
|
||||
app.logger.info "RootPanel start at #{config.web.listen}"
|
||||
app.emit 'app.started'
|
||||
|
||||
@@ -5,8 +5,7 @@
|
||||
process.nextTick ->
|
||||
{Account, Financials, Component} = app.models
|
||||
|
||||
billing = _.extend exports,
|
||||
plans: {}
|
||||
billing = exports
|
||||
|
||||
{available_plugins} = config.extends
|
||||
|
||||
@@ -132,7 +131,7 @@ billing.createPlan = (name, options) ->
|
||||
|
||||
billing.triggerBilling = (account, callback) ->
|
||||
async.each account.plans, (plan, callback) ->
|
||||
billing.plans[plan].triggerBilling account, callback
|
||||
app.plans[plan].triggerBilling account, callback
|
||||
, (err) ->
|
||||
return callback err if err
|
||||
|
||||
@@ -142,15 +141,15 @@ billing.triggerBilling = (account, callback) ->
|
||||
callback err, account
|
||||
|
||||
billing.acceptUsagesBilling = (account, trigger_name, volume, callback) ->
|
||||
plan_names = _.filter billing.plans, (plan) ->
|
||||
plan_names = _.filter app.plans, (plan) ->
|
||||
return plan.billing_trigger[trigger_name] and account.inPlan plan
|
||||
|
||||
async.each plan_names, (plan_name, callback) ->
|
||||
billing.plans[plan_name].acceptUsagesBilling account, trigger_name, volume, callback
|
||||
app.plans[plan_name].acceptUsagesBilling account, trigger_name, volume, callback
|
||||
, callback
|
||||
|
||||
billing.runTimeBilling = (callback = -> ) ->
|
||||
plan_names = _.filter billing.plans, (plan) ->
|
||||
plan_names = _.filter app.plans, (plan) ->
|
||||
return plan.billing_trigger.time
|
||||
|
||||
Account.find
|
||||
@@ -192,7 +191,7 @@ billing.isForceFreeze = (account) ->
|
||||
return false
|
||||
|
||||
billing.joinPlan = (account, plan_name, callback) ->
|
||||
plan = billing.plans[plan_name]
|
||||
plan = app.plans[plan_name]
|
||||
|
||||
modifier =
|
||||
$set: {}
|
||||
@@ -222,12 +221,10 @@ billing.joinPlan = (account, plan_name, callback) ->
|
||||
|
||||
, callback
|
||||
|
||||
, (err) ->
|
||||
console.log 'async.each', arguments
|
||||
callback(err)
|
||||
, callback
|
||||
|
||||
billing.leavePlan = (account, plan_name, callback) ->
|
||||
plan = billing.plans[plan_name]
|
||||
plan = app.plans[plan_name]
|
||||
|
||||
modifier =
|
||||
$unset: {}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
stringify = require 'json-stable-stringify'
|
||||
getParameterNames = require 'get-parameter-names'
|
||||
CounterCache = require 'counter-cache'
|
||||
_ = require 'underscore'
|
||||
|
||||
{redis, config} = app
|
||||
{_} = app.libs
|
||||
|
||||
exports.counter = new CounterCache()
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{_, async} = app.libs
|
||||
{_, async, Q} = app.libs
|
||||
|
||||
app.hooks =
|
||||
app:
|
||||
@@ -77,90 +77,51 @@ exports.selectHookPath = (name, options) ->
|
||||
|
||||
return ref[last]
|
||||
|
||||
exports.applyHooks = (name, account, options = {}) ->
|
||||
{execute, pluck, req} = options
|
||||
exports.getHooks = (name, account, {execute, pluck, req} = {}) ->
|
||||
return _.compact _.flatten exports.selectHookPath(name).map (hook) ->
|
||||
{component, timing} = hook
|
||||
|
||||
result = []
|
||||
|
||||
for hook in exports.selectHookPath(name)
|
||||
template = hook.component
|
||||
timing = hook.timing
|
||||
|
||||
pushResult = (hook, payload = {}) ->
|
||||
result = (params) ->
|
||||
if execute
|
||||
result.push (callback) ->
|
||||
params = []
|
||||
|
||||
{account, node, components, component} = payload
|
||||
|
||||
params.push account if account
|
||||
params.push node if node
|
||||
params.push components if components
|
||||
params.push component if component
|
||||
params.push callback
|
||||
|
||||
hook[execute].apply
|
||||
req: req
|
||||
template: template
|
||||
plugin: hook.plugin
|
||||
, params
|
||||
return hook[execute].apply
|
||||
req: req
|
||||
component: component
|
||||
plugin: hook.plugin
|
||||
, params...
|
||||
|
||||
else if pluck
|
||||
result.push _.extend({}, hook, payload)[pluck]
|
||||
return _.extend({}, hook, params)[pluck]
|
||||
|
||||
else
|
||||
result.push _.extend {}, hook, payload
|
||||
return _.extend {}, hook, params
|
||||
|
||||
if !template or timing == 'always'
|
||||
pushResult hook
|
||||
continue
|
||||
if !component or timing == 'always'
|
||||
return result()
|
||||
|
||||
unless account
|
||||
continue
|
||||
return
|
||||
|
||||
if timing == 'available'
|
||||
if template in account.getAvailableComponentsTemplates()
|
||||
pushResult hook,
|
||||
account: account
|
||||
|
||||
continue
|
||||
if component in account.getAvailableComponentsTemplates()
|
||||
return result account
|
||||
|
||||
components = _.filter account.components, (component) ->
|
||||
return component.template == template.name
|
||||
|
||||
if timing == 'once'
|
||||
unless _.isEmpty components
|
||||
pushResult hook,
|
||||
account: account
|
||||
components: components
|
||||
|
||||
continue
|
||||
return result account, components
|
||||
|
||||
if timing == 'every'
|
||||
for component in components
|
||||
pushResult hook,
|
||||
account: account
|
||||
component: component
|
||||
|
||||
continue
|
||||
return components.map (component) ->
|
||||
return result account, component
|
||||
|
||||
if timing == 'every_node'
|
||||
components_by_node = _.groupBy components, (component) ->
|
||||
return component.node_name
|
||||
return _.each _.groupBy(components, 'node_name'), (node_name, components) ->
|
||||
return result account, app.nodes[node_name], components
|
||||
|
||||
for node_name, components of components_by_node
|
||||
pushResult hook,
|
||||
account: account
|
||||
node: app.nodes[node_name]
|
||||
components: components
|
||||
|
||||
continue
|
||||
|
||||
if execute
|
||||
return (callback) ->
|
||||
async.series result, callback
|
||||
else
|
||||
return result
|
||||
exports.applyHooks = ->
|
||||
return Q.all exports.getHooks arguments...
|
||||
|
||||
error = (message) ->
|
||||
err = new Error 'core.extends.hook: ' + message
|
||||
|
||||
@@ -1,20 +1,64 @@
|
||||
expressBunyanLogger = require 'express-bunyan-logger'
|
||||
expressSession = require 'express-session'
|
||||
redisStore = require 'connect-redis'
|
||||
csrf = require 'csrf'
|
||||
|
||||
{config} = app
|
||||
{_, path, fs, moment, crypto} = app.libs
|
||||
{Account} = app.models
|
||||
{Account, SecurityLog} = app.models
|
||||
|
||||
exports.reqHelpers = (req, res, next) ->
|
||||
req.getCsrfToken = ->
|
||||
if req.headers['x-csrf-token']
|
||||
return req.headers['x-csrf-token']
|
||||
else
|
||||
return req.body.csrf_token
|
||||
|
||||
req.getTokenCode = ->
|
||||
if req.headers['x-token']
|
||||
return req.headers['x-token']
|
||||
else
|
||||
return req.cookies.token
|
||||
|
||||
req.getClientInfo = ->
|
||||
return {
|
||||
ip: req.headers['x-real-ip'] ? req.ip
|
||||
ua: req.headers['user-agent']
|
||||
}
|
||||
|
||||
req.createSecurityLog = (type, payload, options) ->
|
||||
SecurityLog.createLog
|
||||
account: options?.account ? req.account
|
||||
token: token
|
||||
type: type
|
||||
, payload
|
||||
|
||||
exports.errorHandling = (req, res, next) ->
|
||||
res.error = (status, name, param) ->
|
||||
unless _.isNumber status
|
||||
[status, name, param] = [400, status, name]
|
||||
|
||||
if name?.message
|
||||
name = name.message
|
||||
|
||||
param ?= {}
|
||||
|
||||
res.status(status).json _.extend param,
|
||||
error: name.toString()
|
||||
if req.method in ['GET', 'HEAD', 'OPTIONS']
|
||||
res.status(status).send name.toString()
|
||||
else
|
||||
res.status(status).json _.extend param,
|
||||
error: name.toString()
|
||||
|
||||
res.createCookie = (name, value) ->
|
||||
res.cookie name, value,
|
||||
expires: new Date(Date.now() + config.account.cookie_time)
|
||||
|
||||
res.createToken = (account = req.account) ->
|
||||
account.createToken('full_access', req.getClientInfo()).then (token) ->
|
||||
res.createCookie('token', token.code).json
|
||||
account_id: account._id
|
||||
token: token.code
|
||||
|
||||
return token
|
||||
|
||||
next()
|
||||
|
||||
@@ -47,23 +91,17 @@ exports.session = ->
|
||||
secret: secret
|
||||
|
||||
exports.csrf = ->
|
||||
csrf = (require 'csrf')()
|
||||
provider = csrf()
|
||||
|
||||
return (req, res, next) ->
|
||||
csrf_token = do ->
|
||||
if req.headers['x-csrf-token']
|
||||
return req.headers['x-csrf-token']
|
||||
else
|
||||
return req.body.csrf_token
|
||||
|
||||
validator = ->
|
||||
if req.path in _.pluck app.applyHooks('app.ignore_csrf'), 'path'
|
||||
if req.path in app.getHooks('app.ignore_csrf', null, pluck: 'path')
|
||||
return next()
|
||||
|
||||
if req.method in ['GET', 'HEAD', 'OPTIONS']
|
||||
return next()
|
||||
|
||||
unless csrf.verify req.session.csrf_secret, csrf_token
|
||||
unless provider.verify req.session.csrf_secret, req.getCsrfToken()
|
||||
return res.error 403, 'invalid_csrf_token'
|
||||
|
||||
next()
|
||||
@@ -71,31 +109,23 @@ exports.csrf = ->
|
||||
if req.session.csrf_secret
|
||||
return validator()
|
||||
else
|
||||
csrf.secret (err, secret) ->
|
||||
provider.secret (err, secret) ->
|
||||
req.session.csrf_secret = secret
|
||||
req.session.csrf_token = csrf.create secret
|
||||
req.session.csrf_token = provider.create secret
|
||||
|
||||
validator()
|
||||
|
||||
exports.authenticate = (req, res, next) ->
|
||||
token_code = do ->
|
||||
if req.headers['x-token']
|
||||
return req.headers['x-token']
|
||||
else
|
||||
return req.cookies.token
|
||||
code = req.getTokenCode()
|
||||
|
||||
unless token_code
|
||||
unless code
|
||||
return next()
|
||||
|
||||
Account.authenticate token_code, (token, account) ->
|
||||
Account.authenticate(code).then ({token, account}) ->
|
||||
if token and token.type == 'full_access'
|
||||
_.extend req,
|
||||
token: token
|
||||
account: account
|
||||
|
||||
account.populate ->
|
||||
next()
|
||||
|
||||
else
|
||||
next()
|
||||
|
||||
@@ -133,10 +163,8 @@ exports.accountHelpers = (req, res, next) ->
|
||||
|
||||
site_name: req.t(config.web.t_name)
|
||||
|
||||
applyHooks: (name, options) ->
|
||||
app.applyHooks name, req.account, _.extend {
|
||||
execute: false
|
||||
}, options
|
||||
getHooks: (name, options) ->
|
||||
app.getHooks name, req.account, options
|
||||
|
||||
next()
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{pluggable, utils, config, models, mabolo} = app
|
||||
{utils, config, models, mabolo} = app
|
||||
{_, async} = app.libs
|
||||
{Financial, SecurityLog, Component} = app.models
|
||||
{ObjectID} = mabolo
|
||||
@@ -13,7 +13,7 @@ Token = mabolo.model 'Token',
|
||||
required: true
|
||||
type: String
|
||||
|
||||
token:
|
||||
code:
|
||||
required: true
|
||||
type: String
|
||||
|
||||
@@ -30,13 +30,6 @@ Token = mabolo.model 'Token',
|
||||
type: Date
|
||||
default: -> new Date()
|
||||
|
||||
Token::revoke = (callback) ->
|
||||
@parent().update
|
||||
$pull:
|
||||
tokens:
|
||||
token: @token
|
||||
, callback
|
||||
|
||||
Account = mabolo.model 'Account',
|
||||
username:
|
||||
required: true
|
||||
@@ -81,120 +74,99 @@ Account = mabolo.model 'Account',
|
||||
type: Date
|
||||
default: -> new Date()
|
||||
|
||||
# @param account: username, email, password
|
||||
# @param callback(err, account)
|
||||
Account.register = (account, callback) ->
|
||||
Token::revoke = ->
|
||||
@parent().update
|
||||
$pull:
|
||||
tokens:
|
||||
code: @code
|
||||
|
||||
Account.ensureIndex
|
||||
|
||||
Account.register = ({username, email, password}) ->
|
||||
password_salt = utils.randomSalt()
|
||||
password = utils.hashPassword password, password_salt
|
||||
|
||||
{username, email, password} = account
|
||||
avatar_url = '//cdn.v2ex.com/gravatar/' + utils.md5(email)
|
||||
|
||||
account = new @
|
||||
username: username
|
||||
account = new Account
|
||||
email: email
|
||||
password: utils.hashPassword(password, password_salt)
|
||||
username: username
|
||||
password: password
|
||||
password_salt: password_salt
|
||||
|
||||
preferences:
|
||||
avatar_url: "//cdn.v2ex.com/gravatar/#{utils.md5(email)}"
|
||||
avatar_url: avatar_url
|
||||
language: 'auto'
|
||||
timezone: config.i18n.default_timezone
|
||||
|
||||
plans: {}
|
||||
pluggable: {}
|
||||
|
||||
async.each app.applyHooks('account.before_register'), (hook, callback) ->
|
||||
hook.filter account, callback
|
||||
, ->
|
||||
account.save (err) ->
|
||||
callback err, account
|
||||
app.applyHooks('account.before_register',
|
||||
execute: 'filter'
|
||||
).then ->
|
||||
account.save()
|
||||
|
||||
# @param callback(account)
|
||||
Account.search = (stuff, callback) ->
|
||||
@findOne {username: stuff}, (err, account) =>
|
||||
Account.search = (identification) ->
|
||||
@findOne(username: identification).then (account) =>
|
||||
if account
|
||||
return callback account
|
||||
|
||||
@findOne {email: stuff}, (err, account) =>
|
||||
if account
|
||||
return callback account
|
||||
|
||||
@findById stuff, (err, account) ->
|
||||
callback account
|
||||
|
||||
# @param callback(token)
|
||||
Account.generateToken = (callback) ->
|
||||
token = utils.randomSalt()
|
||||
|
||||
@findOne
|
||||
'tokens.token': token
|
||||
, (err, result) ->
|
||||
if result
|
||||
@generateToken callback
|
||||
return account
|
||||
else
|
||||
callback token
|
||||
return @findOne(email: identification).then (account) =>
|
||||
if account
|
||||
return account
|
||||
else
|
||||
return @findById identification
|
||||
|
||||
# @param callback(Token, Account)
|
||||
Account.authenticate = (token, callback) ->
|
||||
unless token
|
||||
return callback()
|
||||
|
||||
@findOneAndUpdate
|
||||
'tokens.token': token
|
||||
Account.authenticate = (token_code) ->
|
||||
@findOneAndUpdate(
|
||||
'tokens.token': token_code
|
||||
,
|
||||
$set:
|
||||
'tokens.$.updated_at': new Date()
|
||||
, (err, account) ->
|
||||
matched_token = _.findWhere account?.tokens,
|
||||
token: token
|
||||
|
||||
callback matched_token, account
|
||||
).then (account) ->
|
||||
return {
|
||||
account: account
|
||||
|
||||
# @param callback(err, token)
|
||||
Account::createToken = (type, payload, callback) ->
|
||||
models.Account.generateToken (code) =>
|
||||
token = new models.Token
|
||||
type: type
|
||||
token: code
|
||||
payload: payload
|
||||
created_at: new Date()
|
||||
updated_at: new Date()
|
||||
token: _.findWhere account?.tokens,
|
||||
code: token_code
|
||||
}
|
||||
|
||||
@update
|
||||
$push:
|
||||
tokens: token
|
||||
, (err) ->
|
||||
callback err, token
|
||||
Account::createToken = (type, payload) ->
|
||||
token = new Token
|
||||
type: type
|
||||
code: utils.randomSalt()
|
||||
payload: payload
|
||||
created_at: new Date()
|
||||
updated_at: new Date()
|
||||
|
||||
@update(
|
||||
$push:
|
||||
tokens: token
|
||||
).thenResolve token
|
||||
|
||||
Account::matchPassword = (password) ->
|
||||
return @password == utils.hashPassword(password, @password_salt)
|
||||
|
||||
Account::updatePassword = (password, callback) ->
|
||||
@password_salt = utils.randomSalt()
|
||||
@password = utils.hashPassword password, @password_salt
|
||||
@save callback
|
||||
Account::setPassword = (password) ->
|
||||
password_salt = utils.randomSalt()
|
||||
password = utils.hashPassword password, password_salt
|
||||
|
||||
# @param callback(err)
|
||||
Account::incBalance = (amount, type, payload, callback) ->
|
||||
unless _.isNumber amount
|
||||
return callback 'invalid_amount'
|
||||
@update
|
||||
$set:
|
||||
password: password
|
||||
password_salt: password_salt
|
||||
|
||||
financials = new models.Financials
|
||||
account_id: @_id
|
||||
type: type
|
||||
amount: amount
|
||||
payload: payload
|
||||
Account::setEmail = (email) ->
|
||||
|
||||
financials.validate (err) =>
|
||||
return callback err if err
|
||||
Account::updatePreferences = (preferences) ->
|
||||
|
||||
Account::increaseBalance = (amount, type, payload) ->
|
||||
Financials.createLog(@, type, amount, payload).then =>
|
||||
@update
|
||||
$inc:
|
||||
balance: amount
|
||||
, (err) ->
|
||||
return callback err if err
|
||||
|
||||
financials.save (err) ->
|
||||
callback err
|
||||
|
||||
Account::inGroup = (group) ->
|
||||
return group in @groups
|
||||
@@ -205,22 +177,11 @@ Account::isAdmin = ->
|
||||
Account::inPlan = (plan_name) ->
|
||||
return plan_name in _.keys @plans
|
||||
|
||||
Account::createSecurityLog = (type, token, payload, callback) ->
|
||||
SecurityLog.create
|
||||
account_id: @_id
|
||||
type: type
|
||||
token: _.pick token, 'type', 'token', 'created_at', 'payload'
|
||||
payload: payload
|
||||
, callback
|
||||
|
||||
Account::availableComponentsTemplates = ->
|
||||
return _.uniq _.flatten _.compact _.map _.keys(@plans), (plan_name) ->
|
||||
return _.keys app.plans[plan_name].available_components
|
||||
|
||||
Account::populate = (callback) ->
|
||||
async.parallel
|
||||
components: (callback) =>
|
||||
Component.getComponents @, callback
|
||||
|
||||
, (err, result) =>
|
||||
callback _.extend @, result
|
||||
Account::populate = ->
|
||||
Component.getComponents(@).then (components) =>
|
||||
return _.extend @,
|
||||
components: components
|
||||
|
||||
@@ -44,15 +44,13 @@ Component = mabolo.model 'Component',
|
||||
|
||||
coworkers: [Coworker]
|
||||
|
||||
Component.getComponents = (account, callback) ->
|
||||
Component.getComponents = (account) ->
|
||||
@find
|
||||
$or: [
|
||||
account_id: account._id
|
||||
,
|
||||
'coworkers.account_id': account._id
|
||||
]
|
||||
, (err, components) ->
|
||||
callback err, components
|
||||
|
||||
Component::hasMember = (account) ->
|
||||
if @account_id.equals account._id
|
||||
@@ -61,11 +59,10 @@ Component::hasMember = (account) ->
|
||||
return _.some @coworkers, (coworker) ->
|
||||
return coworker.account_id.equals account._id
|
||||
|
||||
Component::markAsStatus = (status, callback) ->
|
||||
Component::markAsStatus = (status) ->
|
||||
@update
|
||||
$set:
|
||||
status: status
|
||||
, callback
|
||||
|
||||
Component::populate = (callback) ->
|
||||
{Account} = app.models
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{utils, config, mabolo} = app
|
||||
{_, ObjectID
|
||||
, mongoose} = app.libs
|
||||
{_} = app.libs
|
||||
{ObjectID} = mabolo
|
||||
|
||||
ApplyLog = mabolo.model 'ApplyLog',
|
||||
account_id:
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
{mabolo} = app
|
||||
{_, ObjectID
|
||||
, mongoose} = app.libs
|
||||
{ObjectID} = mabolo
|
||||
|
||||
Financials = mabolo.model 'Financials',
|
||||
account_id:
|
||||
required: true
|
||||
type: ObjectID
|
||||
|
||||
ref: 'Account'
|
||||
|
||||
type:
|
||||
@@ -25,3 +22,14 @@ Financials = mabolo.model 'Financials',
|
||||
|
||||
payload:
|
||||
type: Object
|
||||
|
||||
Financials.createLog = (account, type, amount, payload) ->
|
||||
Q().then ->
|
||||
unless isFinite amount
|
||||
throw new Error 'invalid_amount'
|
||||
|
||||
@create
|
||||
account_id: account._id
|
||||
type: type
|
||||
amount: amount
|
||||
payload: payload
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
Notification = mabolo.model 'Notification',
|
||||
account_id:
|
||||
type: ObjectID
|
||||
|
||||
ref: 'Account'
|
||||
|
||||
group_name:
|
||||
@@ -27,3 +26,52 @@ Notification = mabolo.model 'Notification',
|
||||
|
||||
payload:
|
||||
type: Object
|
||||
|
||||
notices_level =
|
||||
ticket_create: 'notice'
|
||||
ticket_reply: 'notice'
|
||||
ticket_update: 'event'
|
||||
|
||||
Notification.createNotice = (account, type, notice) ->
|
||||
level = notices_level[type]
|
||||
|
||||
Notification.create
|
||||
account_id: account._id
|
||||
type: type
|
||||
level: level
|
||||
payload: notice
|
||||
.then ->
|
||||
app.mailer.sendMail
|
||||
from: config.email.send_from
|
||||
to: account.email
|
||||
subject: notice.title
|
||||
html: notice.body
|
||||
, ->
|
||||
callback notification
|
||||
|
||||
Notification.createGroupNotice = (group, type, notice) ->
|
||||
level = exports.notices_level[type]
|
||||
|
||||
notification = new Notification
|
||||
group_name: group
|
||||
type: type
|
||||
level: level
|
||||
payload: notice
|
||||
|
||||
notification.save ->
|
||||
unless level == NOTICE
|
||||
callback notification
|
||||
|
||||
Account.find
|
||||
groups: 'root'
|
||||
, (err, accounts) ->
|
||||
async.each accounts, (account, callback) ->
|
||||
app.mailer.sendMail
|
||||
from: config.email.send_from
|
||||
to: account.email
|
||||
subject: notice.title
|
||||
html: notice.body
|
||||
, callback
|
||||
, (err) ->
|
||||
logger.error err if err
|
||||
callback notification
|
||||
|
||||
@@ -11,7 +11,10 @@ SecurityLog = mabolo.model 'SecurityLog',
|
||||
type:
|
||||
required: true
|
||||
type: String
|
||||
enum: ['revoke_token', 'update_password', 'update_email', 'update_preferences']
|
||||
enum: [
|
||||
'login', 'revoke_token'
|
||||
'update_password', 'update_email', 'update_preferences'
|
||||
]
|
||||
|
||||
created_at:
|
||||
type: Date
|
||||
@@ -22,3 +25,10 @@ SecurityLog = mabolo.model 'SecurityLog',
|
||||
|
||||
token:
|
||||
type: Object
|
||||
|
||||
SecurityLog.createLog = ({account, token, type}, payload) ->
|
||||
@create
|
||||
account_id: account._id
|
||||
payload: payload
|
||||
token: _.pick token, 'type', 'token', 'created_at', 'payload'
|
||||
type: type
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
markdown = require('markdown').markdown
|
||||
{markdown} = require 'markdown'
|
||||
|
||||
{models, logger, mabolo} = app
|
||||
{_, async} = app.libs
|
||||
@@ -59,59 +59,55 @@ Ticket = mabolo.model 'Ticket',
|
||||
type: Date
|
||||
default: -> new Date()
|
||||
|
||||
Ticket.create = (account, ticket, callback) ->
|
||||
{title, content, status} = ticket
|
||||
|
||||
Ticket.create = (account, {title, content, status}) ->
|
||||
unless title?.trim()
|
||||
return callback 'empty_title'
|
||||
throw new Error 'empty_title'
|
||||
|
||||
if account.isAdmin()
|
||||
status ?= 'open'
|
||||
else
|
||||
status = 'pending'
|
||||
|
||||
# TODO: replace `ObjectID account._id.toString()` to `account._id`
|
||||
|
||||
@__super__.constructor.create.call @,
|
||||
account_id: ObjectID account._id.toString()
|
||||
account_id: account._id
|
||||
title: title
|
||||
status: status
|
||||
content: content
|
||||
content_html: markdown.toHTML content
|
||||
members: [ObjectID account._id.toString()]
|
||||
members: [account._id]
|
||||
replies: []
|
||||
, callback
|
||||
|
||||
Ticket::hasMember = (account) ->
|
||||
return _.some @members, (member_id) ->
|
||||
return member_id.equals account._id
|
||||
|
||||
Ticket::setStatusByAccount = (account, status, callback) ->
|
||||
Ticket::setStatusByAccount = (account, status) ->
|
||||
if account.isAdmin()
|
||||
unless status in ['open', 'pending', 'finish', 'closed']
|
||||
return callback 'invalid_status'
|
||||
throw new Error 'invalid_status'
|
||||
else
|
||||
unless status in ['closed']
|
||||
return callback 'invalid_status'
|
||||
throw new Error 'invalid_status'
|
||||
|
||||
@setStatus status, callback
|
||||
@setStatus status
|
||||
|
||||
Ticket::setStatus = (status, callback) ->
|
||||
# TODO: validate status
|
||||
Ticket::setStatus = (status) ->
|
||||
unless status in ['open', 'pending', 'finish', 'closed']
|
||||
throw new Error 'invalid_status'
|
||||
|
||||
@update
|
||||
$set:
|
||||
status: status
|
||||
updated_at: new Date()
|
||||
, callback
|
||||
|
||||
Ticket::createReply = (account, reply, callback) ->
|
||||
Ticket::createReply = (account, {content, status}, callback) ->
|
||||
{content, status} = reply
|
||||
|
||||
# TODO: cant reply after closed
|
||||
if @status == 'closed'
|
||||
throw new Error 'already_closed'
|
||||
|
||||
unless content?.trim()
|
||||
return callback 'empty_content'
|
||||
throw new Error 'empty_content'
|
||||
|
||||
if account.isAdmin()
|
||||
status ?= 'open'
|
||||
@@ -119,47 +115,33 @@ Ticket::createReply = (account, reply, callback) ->
|
||||
status = 'pending'
|
||||
|
||||
reply = new Reply
|
||||
_parent: @
|
||||
account_id: account._id
|
||||
content: content
|
||||
content_html: markdown.toHTML content
|
||||
created_at: new Date()
|
||||
|
||||
@update
|
||||
@update(
|
||||
$push:
|
||||
replies: reply
|
||||
$set:
|
||||
status: status
|
||||
updated_at: new Date()
|
||||
, (err) ->
|
||||
callback err, reply
|
||||
).thenResolve reply
|
||||
|
||||
Ticket::populateAccounts = (callback) ->
|
||||
{Account} = app.models
|
||||
Ticket::populateAccounts = ->
|
||||
app.models.Account.find
|
||||
_id:
|
||||
$in: [
|
||||
@account_id, @members..., _.pluck(@replies, 'account_id')...
|
||||
]
|
||||
|
||||
async.parallel [
|
||||
(callback) =>
|
||||
Account.findById @account_id, (err, account) =>
|
||||
@account = account
|
||||
callback err
|
||||
.then (accounts) =>
|
||||
@account = _.find accounts, (i) =>
|
||||
return @account_id.equals i
|
||||
|
||||
(callback) =>
|
||||
Account.find
|
||||
_id:
|
||||
$in: @members
|
||||
, (err, accounts) =>
|
||||
@members = accounts
|
||||
callback err
|
||||
@members = _.filter accounts, (i) =>
|
||||
return @hasMember i
|
||||
|
||||
(callback) =>
|
||||
Account.find
|
||||
_id:
|
||||
$in: _.pluck @replies, 'account_id'
|
||||
, (err, accounts) =>
|
||||
for reply in @replies
|
||||
reply.account = _.find accounts, (account) ->
|
||||
return reply.account_id.equals account._id
|
||||
|
||||
callback err
|
||||
|
||||
], callback
|
||||
for reply in @replies
|
||||
reply.account = _.find accounts, (i) ->
|
||||
return reply.account_id.equals account._id
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
{async, _} = app.libs
|
||||
{i18n, config, logger, mailer} = app
|
||||
{Account, Notification} = app.models
|
||||
|
||||
{NOTICE, EVENT, LOG} = _.extend exports,
|
||||
NOTICE: 'notice'
|
||||
EVENT: 'event'
|
||||
LOG: 'log'
|
||||
|
||||
exports.notices_level = notices_level =
|
||||
ticket_create: NOTICE
|
||||
ticket_reply: NOTICE
|
||||
ticket_update: EVENT
|
||||
|
||||
exports.createNotice = (account, type, notice, callback) ->
|
||||
level = exports.notices_level[type]
|
||||
|
||||
notification = new Notification
|
||||
account_id: account._id
|
||||
type: type
|
||||
level: level
|
||||
payload: notice
|
||||
|
||||
notification.save ->
|
||||
app.mailer.sendMail
|
||||
from: config.email.send_from
|
||||
to: account.email
|
||||
subject: notice.title
|
||||
html: notice.body
|
||||
, ->
|
||||
callback notification
|
||||
|
||||
exports.createGroupNotice = (group, type, notice, callback) ->
|
||||
level = exports.notices_level[type]
|
||||
|
||||
notification = new Notification
|
||||
group_name: group
|
||||
type: type
|
||||
level: level
|
||||
payload: notice
|
||||
|
||||
notification.save ->
|
||||
unless level == NOTICE
|
||||
callback notification
|
||||
|
||||
Account.find
|
||||
groups: 'root'
|
||||
, (err, accounts) ->
|
||||
async.each accounts, (account, callback) ->
|
||||
app.mailer.sendMail
|
||||
from: config.email.send_from
|
||||
to: account.email
|
||||
subject: notice.title
|
||||
html: notice.body
|
||||
, callback
|
||||
, (err) ->
|
||||
logger.error err if err
|
||||
callback notification
|
||||
@@ -1,6 +1,6 @@
|
||||
{_, async, express} = app.libs
|
||||
{_, express} = app.libs
|
||||
{requireAuthenticate} = app.middleware
|
||||
{Account, SecurityLog} = app.models
|
||||
{Account, SecurityLog, CouponCode} = app.models
|
||||
{config, utils, logger, i18n} = app
|
||||
|
||||
module.exports = exports = express.Router()
|
||||
@@ -33,68 +33,42 @@ exports.get '/session_info/', (req, res) ->
|
||||
res.json response
|
||||
|
||||
exports.post '/register', (req, res) ->
|
||||
Account.register req.body, (err, account) ->
|
||||
return res.error utils.pickErrorName err if err
|
||||
|
||||
account.createToken 'full_access',
|
||||
ip: req.headers['x-real-ip']
|
||||
ua: req.headers['user-agent']
|
||||
, (err, token) ->
|
||||
logger.error err if err
|
||||
|
||||
res.cookie 'token', token.token,
|
||||
expires: new Date(Date.now() + config.account.cookie_time)
|
||||
|
||||
res.json
|
||||
id: account._id
|
||||
Account.register(req.body).then (account) ->
|
||||
res.createToken account
|
||||
.catch res.error
|
||||
|
||||
exports.post '/login', (req, res) ->
|
||||
Account.search req.body.username, (account) ->
|
||||
unless account
|
||||
return res.error 'wrong_password'
|
||||
Account.search(req.body.username).then (account) ->
|
||||
if account?.matchPassword req.body.password
|
||||
throw new Error 'wrong_password'
|
||||
|
||||
unless account.matchPassword req.body.password
|
||||
return res.error 'wrong_password'
|
||||
res.createCookie 'language', account.preferences.language
|
||||
|
||||
account.createToken 'full_access',
|
||||
ip: req.headers['x-real-ip']
|
||||
ua: req.headers['user-agent']
|
||||
, (err, token) ->
|
||||
logger.error err if err
|
||||
|
||||
res.cookie 'token', token.token,
|
||||
expires: new Date Date.now() + config.account.cookie_time
|
||||
|
||||
res.cookie 'language', account.preferences.language,
|
||||
expires: new Date Date.now() + config.account.cookie_time
|
||||
|
||||
res.json
|
||||
id: account._id
|
||||
res.createToken(account).then (token) ->
|
||||
req.createSecurityLog 'login', {},
|
||||
account: account
|
||||
token: token
|
||||
|
||||
exports.post '/logout', requireAuthenticate, (req, res) ->
|
||||
req.token.revoke ->
|
||||
req.account.createSecurityLog 'revoke_token', req.token,
|
||||
revoke_ip: req.headers['x-real-ip']
|
||||
revoke_ua: req.headers['user-agent']
|
||||
, (err) ->
|
||||
logger.error err if err
|
||||
.catch res.error
|
||||
|
||||
res.clearCookie 'token'
|
||||
res.json {}
|
||||
exports.post '/logout', requireAuthenticate, (req, res) ->
|
||||
req.token.revoke().then ->
|
||||
req.createSecurityLog('revoke_token').then ->
|
||||
res.clearCookie('token').sendStatus 204
|
||||
.catch res.error
|
||||
|
||||
exports.post '/update_password', requireAuthenticate, (req, res) ->
|
||||
unless req.account.matchPassword req.body.original_password
|
||||
return res.error 'wrong_password'
|
||||
Q().then ->
|
||||
unless req.account.matchPassword req.body.original_password
|
||||
throw new Error 'wrong_password'
|
||||
|
||||
unless utils.rx.password.test req.body.password
|
||||
return res.error 'invalid_password'
|
||||
unless utils.rx.password.test req.body.password
|
||||
throw new Error 'invalid_password'
|
||||
|
||||
req.account.updatePassword req.body.password, ->
|
||||
req.account.createSecurityLog 'update_password', req.token, {}, (err) ->
|
||||
logger.error err if err
|
||||
req.account.setPassword(req.body.password).then ->
|
||||
req.createSecurityLog 'update_password'
|
||||
|
||||
res.json {}
|
||||
.catch res.error
|
||||
|
||||
exports.post '/update_email', requireAuthenticate, (req, res) ->
|
||||
unless req.account.matchPassword req.body.password
|
||||
@@ -136,3 +110,43 @@ exports.post '/update_preferences', requireAuthenticate, (req, res) ->
|
||||
logger.error err if err
|
||||
|
||||
res.json {}
|
||||
|
||||
exports.use do ->
|
||||
router = new express.Router()
|
||||
|
||||
router.use requireAuthenticate
|
||||
|
||||
router.get '/info', (req, res) ->
|
||||
CouponCode.findOne
|
||||
code: req.query.code
|
||||
, (err, coupon) ->
|
||||
unless coupon
|
||||
return res.error 'code_not_exist'
|
||||
|
||||
coupon.validateCode req.account, (is_available) ->
|
||||
unless is_available
|
||||
return res.error 'code_not_available'
|
||||
|
||||
coupon.getMessage req, (message) ->
|
||||
res.json
|
||||
message: message
|
||||
|
||||
router.post '/apply', (req, res) ->
|
||||
CouponCode.findOne
|
||||
code: req.body.code
|
||||
, (err, coupon) ->
|
||||
unless coupon
|
||||
return res.error 'code_not_exist'
|
||||
|
||||
if coupon.expired and Date.now() > coupon.expired.getTime()
|
||||
return res.error 'code_expired'
|
||||
|
||||
if coupon.available_times and coupon.available_times < 0
|
||||
return res.error 'code_not_available'
|
||||
|
||||
coupon.validateCode req.account, (is_available) ->
|
||||
unless is_available
|
||||
return res.error 'code_not_available'
|
||||
|
||||
coupon.applyCode req.account, ->
|
||||
res.json {}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{express, async, _} = app.libs
|
||||
{requireAdminAuthenticate} = app.middleware
|
||||
{Account, Ticket, Financials, CouponCode} = app.models
|
||||
{config, pluggable} = app
|
||||
{config} = app
|
||||
|
||||
module.exports = exports = express.Router()
|
||||
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
{express, _} = app.libs
|
||||
{config, billing} = app
|
||||
{requireAuthenticate} = app.middleware
|
||||
{Account} = app.models
|
||||
|
||||
module.exports = exports = express.Router()
|
||||
|
||||
exports.use requireAuthenticate
|
||||
|
||||
{when_balance_below} = config.billing.force_freeze
|
||||
|
||||
exports.post '/join_plan', (req, res) ->
|
||||
{plan} = req.body
|
||||
|
||||
unless billing.plans[plan]
|
||||
return res.error 'invalid_plan'
|
||||
|
||||
if req.account.inPlan plan
|
||||
return res.error 'already_in_plan'
|
||||
|
||||
if req.account.balance <= when_balance_below
|
||||
return res.error 'insufficient_balance'
|
||||
|
||||
billing.joinPlan req.account, plan, (err) ->
|
||||
console.log err
|
||||
if err
|
||||
res.error err
|
||||
else
|
||||
res.status(204).json {}
|
||||
|
||||
exports.post '/leave_plan', (req, res) ->
|
||||
{plan} = req.body
|
||||
|
||||
unless req.account.inPlan plan
|
||||
return res.error 'not_in_plan'
|
||||
|
||||
billing.leavePlan req.account, plan, (err) ->
|
||||
if err
|
||||
res.error err
|
||||
else
|
||||
res.status(204).json {}
|
||||
@@ -1,43 +0,0 @@
|
||||
{_, express} = app.libs
|
||||
{requireAuthenticate} = app.middleware
|
||||
{CouponCode} = app.models
|
||||
{config, utils, logger} = app
|
||||
|
||||
module.exports = exports = express.Router()
|
||||
|
||||
exports.use requireAuthenticate
|
||||
|
||||
exports.get '/info', (req, res) ->
|
||||
CouponCode.findOne
|
||||
code: req.query.code
|
||||
, (err, coupon) ->
|
||||
unless coupon
|
||||
return res.error 'code_not_exist'
|
||||
|
||||
coupon.validateCode req.account, (is_available) ->
|
||||
unless is_available
|
||||
return res.error 'code_not_available'
|
||||
|
||||
coupon.getMessage req, (message) ->
|
||||
res.json
|
||||
message: message
|
||||
|
||||
exports.post '/apply', (req, res) ->
|
||||
CouponCode.findOne
|
||||
code: req.body.code
|
||||
, (err, coupon) ->
|
||||
unless coupon
|
||||
return res.error 'code_not_exist'
|
||||
|
||||
if coupon.expired and Date.now() > coupon.expired.getTime()
|
||||
return res.error 'code_expired'
|
||||
|
||||
if coupon.available_times and coupon.available_times < 0
|
||||
return res.error 'code_not_available'
|
||||
|
||||
coupon.validateCode req.account, (is_available) ->
|
||||
unless is_available
|
||||
return res.error 'code_not_available'
|
||||
|
||||
coupon.applyCode req.account, ->
|
||||
res.json {}
|
||||
@@ -1,12 +1,43 @@
|
||||
{express, async, _} = app.libs
|
||||
{requireAuthenticate} = app.middleware
|
||||
{Account, Financials} = app.models
|
||||
{pluggable, billing, config} = app
|
||||
{billing, config} = app
|
||||
|
||||
module.exports = exports = express.Router()
|
||||
|
||||
exports.use requireAuthenticate
|
||||
|
||||
exports.post '/join_plan', (req, res) ->
|
||||
{plan} = req.body
|
||||
|
||||
unless billing.plans[plan]
|
||||
return res.error 'invalid_plan'
|
||||
|
||||
if req.account.inPlan plan
|
||||
return res.error 'already_in_plan'
|
||||
|
||||
if req.account.balance <= when_balance_below
|
||||
return res.error 'insufficient_balance'
|
||||
|
||||
billing.joinPlan req.account, plan, (err) ->
|
||||
console.log err
|
||||
if err
|
||||
res.error err
|
||||
else
|
||||
res.status(204).json {}
|
||||
|
||||
exports.post '/leave_plan', (req, res) ->
|
||||
{plan} = req.body
|
||||
|
||||
unless req.account.inPlan plan
|
||||
return res.error 'not_in_plan'
|
||||
|
||||
billing.leavePlan req.account, plan, (err) ->
|
||||
if err
|
||||
res.error err
|
||||
else
|
||||
res.status(204).json {}
|
||||
|
||||
exports.get '/financials', (req, res) ->
|
||||
LIMIT = 10
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{_, async, express} = app.libs
|
||||
{requireAuthenticate, TODO} = app.middleware
|
||||
{Account, Ticket} = app.models
|
||||
{config, notification, logger} = app
|
||||
{config, logger} = app
|
||||
|
||||
module.exports = exports = express.Router()
|
||||
|
||||
@@ -25,7 +25,7 @@ ticketParam = (req, res, next, id) ->
|
||||
|
||||
exports.param 'id', ticketParam
|
||||
|
||||
exports.use '/resource', do ->
|
||||
exports.use '/rest', do ->
|
||||
rest = new express.Router mergeParams: true
|
||||
rest.param 'id', ticketParam
|
||||
|
||||
|
||||
@@ -1,14 +1,5 @@
|
||||
$ ->
|
||||
$('.action-login').click ->
|
||||
RP.request '/account/login',
|
||||
username: $('[name=username]').val()
|
||||
password: $('[name=password]').val()
|
||||
, ->
|
||||
location.href = '/panel/'
|
||||
|
||||
$('[name=password]').keypress (e) ->
|
||||
if e.keyCode == 13
|
||||
$('.action-login').click()
|
||||
{request, t} = RP
|
||||
|
||||
$('.action-register').click ->
|
||||
username = $('[name=username]').val()
|
||||
@@ -19,9 +10,56 @@ $ ->
|
||||
unless password == password2
|
||||
return alert t 'view.account.password_inconsistent'
|
||||
|
||||
RP.request '/account/register',
|
||||
request '/account/register',
|
||||
username: username
|
||||
password: password
|
||||
email: email
|
||||
, ->
|
||||
location.href = '/panel/'
|
||||
|
||||
$('.action-login').click ->
|
||||
request '/account/login',
|
||||
username: $('[name=username]').val()
|
||||
password: $('[name=password]').val()
|
||||
, ->
|
||||
location.href = '/panel/'
|
||||
|
||||
$('[name=password]').keypress (e) ->
|
||||
if e.keyCode == 13
|
||||
$('.action-login').click()
|
||||
|
||||
$('.action-save').click ->
|
||||
request '/account/update_preferences',
|
||||
qq: $('[name=qq]').val()
|
||||
, ->
|
||||
alert t 'common.success'
|
||||
|
||||
$('.action-use').click ->
|
||||
code = $('[name=coupon_code]').val()
|
||||
|
||||
request "/coupon/info?code=#{code}", {}, {method: 'get'}, (result) ->
|
||||
if window.confirm result.message
|
||||
request '/coupon/apply',
|
||||
code: code
|
||||
, ->
|
||||
alert t 'common.success'
|
||||
|
||||
$('.action-update-password').click ->
|
||||
password = $('.form-password .input-password').val()
|
||||
password2 = $('.form-password .input-password2').val()
|
||||
|
||||
if password != password2
|
||||
return alert t 'view.account.password_inconsistent'
|
||||
|
||||
request '/account/update_password/',
|
||||
original_password: $('.form-password .input-original_password').val()
|
||||
password: password
|
||||
, ->
|
||||
alert t 'common.success'
|
||||
|
||||
$('.action-update-email').click ->
|
||||
request '/account/update_email/',
|
||||
password: $('.form-email .input-password').val()
|
||||
email: $('.form-email .input-email').val()
|
||||
, ->
|
||||
alert t 'common.success'
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
$ ->
|
||||
$('.action-save').click ->
|
||||
request '/account/update_preferences',
|
||||
qq: $('.form-setting .input-qq').val()
|
||||
, ->
|
||||
alert t 'common.success'
|
||||
|
||||
$('.action-use').click ->
|
||||
code = $('.form-coupon .input-coupon_code').val()
|
||||
|
||||
request "/coupon/info?code=#{code}", {}, {method: 'get'}, (result) ->
|
||||
if window.confirm result.message
|
||||
request '/coupon/apply',
|
||||
code: code
|
||||
, ->
|
||||
alert t 'common.success'
|
||||
|
||||
$('.action-update-password').click ->
|
||||
password = $('.form-password .input-password').val()
|
||||
password2 = $('.form-password .input-password2').val()
|
||||
|
||||
if password != password2
|
||||
return alert t 'view.account.password_inconsistent'
|
||||
|
||||
request '/account/update_password/',
|
||||
original_password: $('.form-password .input-original_password').val()
|
||||
password: password
|
||||
, ->
|
||||
alert t 'common.success'
|
||||
|
||||
$('.action-update-email').click ->
|
||||
request '/account/update_email/',
|
||||
password: $('.form-email .input-password').val()
|
||||
email: $('.form-email .input-email').val()
|
||||
, ->
|
||||
alert t 'common.success'
|
||||
@@ -1,9 +0,0 @@
|
||||
{fs, path} = app.libs
|
||||
|
||||
template_data = {}
|
||||
|
||||
for filename in fs.readdirSync "#{__dirname}/template"
|
||||
template_name = path.basename filename, path.extname(filename)
|
||||
template_data[template_name] = fs.readFileSync("#{__dirname}/template/#{filename}").toString()
|
||||
|
||||
module.exports = template_data
|
||||
141
core/test/account.register.test.coffee
Normal file
141
core/test/account.register.test.coffee
Normal file
@@ -0,0 +1,141 @@
|
||||
describe 'account.register.test', ->
|
||||
agent = null
|
||||
csrf_token = null
|
||||
|
||||
account_id = null
|
||||
username = null
|
||||
password = null
|
||||
email = null
|
||||
|
||||
before ->
|
||||
agent = supertest.agent app.express
|
||||
|
||||
after (done) ->
|
||||
cleanUpByAccount account_id, done
|
||||
|
||||
it 'GET login', (done) ->
|
||||
agent.get '/account/login'
|
||||
.expect 200
|
||||
.end done
|
||||
|
||||
it 'GET register', (done) ->
|
||||
agent.get '/account/register'
|
||||
.expect 200
|
||||
.end done
|
||||
|
||||
it 'GET session_info', (done) ->
|
||||
agent.get '/account/session_info'
|
||||
.expect 200
|
||||
.end (err, res) ->
|
||||
res.body.csrf_token.should.be.exist
|
||||
csrf_token = res.body.csrf_token
|
||||
done err
|
||||
|
||||
it 'POST register', (done) ->
|
||||
username = 'test' + utils.randomString(8).toLowerCase()
|
||||
password = utils.randomString 8
|
||||
email = utils.randomString(8) + '@gmail.com'
|
||||
|
||||
agent.post '/account/register'
|
||||
.send
|
||||
csrf_token: csrf_token
|
||||
username: username
|
||||
email: email
|
||||
password: password
|
||||
.expect 200
|
||||
.expect 'set-cookie', /token=/
|
||||
.end (err, res) ->
|
||||
res.body.id.should.have.length 24
|
||||
account_id = res.body.id
|
||||
done err
|
||||
|
||||
it 'POST register with existed username', (done) ->
|
||||
agent.post '/account/register'
|
||||
.send
|
||||
csrf_token: csrf_token
|
||||
username: username
|
||||
email: "#{utils.randomString 8}@gmail.com"
|
||||
password: password
|
||||
.expect 400
|
||||
.end (err, res) ->
|
||||
res.body.error.should.be.equal 'username_exist'
|
||||
done err
|
||||
|
||||
it 'POST register with invalid email', (done) ->
|
||||
agent.post '/account/register'
|
||||
.send
|
||||
csrf_token: csrf_token
|
||||
username: "test#{utils.randomString(8).toLowerCase()}"
|
||||
email: "@gmail.com"
|
||||
password: password
|
||||
.expect 400
|
||||
.end (err, res) ->
|
||||
res.body.error.should.be.equal 'invalid_email'
|
||||
done err
|
||||
|
||||
it 'POST login', (done) ->
|
||||
agent.post '/account/login'
|
||||
.send
|
||||
csrf_token: csrf_token
|
||||
username: username
|
||||
password: password
|
||||
.expect 200
|
||||
.expect 'set-cookie', /token=/
|
||||
.end (err, res) ->
|
||||
res.body.id.should.be.equal account_id
|
||||
res.body.token.should.be.exist
|
||||
done err
|
||||
|
||||
it 'GET session_info when logged', (done) ->
|
||||
agent.get '/account/session_info'
|
||||
.expect 200
|
||||
.end (err, res) ->
|
||||
res.body.csrf_token.should.be.exist
|
||||
res.body.username.should.be.equal username
|
||||
res.body.preferences.should.be.a 'object'
|
||||
done err
|
||||
|
||||
it 'POST logout', (done) ->
|
||||
agent.post '/account/logout'
|
||||
.send
|
||||
csrf_token: csrf_token
|
||||
.expect 200
|
||||
.expect 'set-cookie', /token=;/
|
||||
.end done
|
||||
|
||||
it 'POST login with email', (done) ->
|
||||
agent.post '/account/login'
|
||||
.send
|
||||
csrf_token: csrf_token
|
||||
username: email.toLowerCase()
|
||||
password: password
|
||||
.expect 200
|
||||
.expect 'set-cookie', /token=/
|
||||
.end (err, res) ->
|
||||
res.body.id.should.be.equal account_id
|
||||
res.body.token.should.be.exist
|
||||
done err
|
||||
|
||||
it 'POST login with username does not exist', (done) ->
|
||||
agent.post '/account/login'
|
||||
.send
|
||||
csrf_token: csrf_token
|
||||
username: 'username_not_exist'
|
||||
password: password
|
||||
.expect 400
|
||||
.end (err, res) ->
|
||||
res.body.error.should.be.equal 'wrong_password'
|
||||
expect(res.body.token).to.not.exist
|
||||
done err
|
||||
|
||||
it 'POST login with invalid password', (done) ->
|
||||
agent.post '/account/login'
|
||||
.send
|
||||
csrf_token: csrf_token
|
||||
username: username
|
||||
password: 'invalid password'
|
||||
.expect 400
|
||||
.end (err, res) ->
|
||||
res.body.error.should.be.equal 'wrong_password'
|
||||
expect(res.body.token).to.not.exist
|
||||
done err
|
||||
@@ -1,4 +1,4 @@
|
||||
describe 'ticket.test', ->
|
||||
describe 'ticket.user.test', ->
|
||||
agent = null
|
||||
csrf_token = null
|
||||
|
||||
@@ -10,7 +10,7 @@ describe 'ticket.test', ->
|
||||
done err
|
||||
|
||||
it 'POST /', (done) ->
|
||||
agent.post '/ticket/resource/'
|
||||
agent.post '/ticket/rest/'
|
||||
.send
|
||||
csrf_token: csrf_token
|
||||
title: 'Title'
|
||||
@@ -22,14 +22,14 @@ describe 'ticket.test', ->
|
||||
done err
|
||||
|
||||
it 'GET /', (done) ->
|
||||
agent.get '/ticket/resource/'
|
||||
agent.get '/ticket/rest/'
|
||||
.expect 200
|
||||
.end (err, res) ->
|
||||
res.body.should.be.a 'array'
|
||||
done err
|
||||
|
||||
it 'POST /:id/replies', (done) ->
|
||||
agent.post "/ticket/resource/#{ticket_id}/replies"
|
||||
agent.post "/ticket/rest/#{ticket_id}/replies"
|
||||
.send
|
||||
csrf_token: csrf_token
|
||||
content: 'Reply'
|
||||
@@ -40,14 +40,14 @@ describe 'ticket.test', ->
|
||||
done err
|
||||
|
||||
it 'GET /:id', (done) ->
|
||||
agent.get "/ticket/resource/#{ticket_id}"
|
||||
agent.get "/ticket/rest/#{ticket_id}"
|
||||
.expect 200
|
||||
.end (err, res) ->
|
||||
res.body.replies.length.should.be.equal 1
|
||||
done err
|
||||
|
||||
it 'PUT /:id/status', (done) ->
|
||||
agent.put "/ticket/resource/#{ticket_id}/status"
|
||||
agent.put "/ticket/rest/#{ticket_id}/status"
|
||||
.send
|
||||
csrf_token: csrf_token
|
||||
status: 'closed'
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
extends ../layout
|
||||
|
||||
prepend header
|
||||
title #{t('account.login')} | #{t(config.web.t_name)}
|
||||
title #{t('account.login')} | #{site_name}
|
||||
|
||||
block main
|
||||
header= t('account.login')
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
extends ../layout
|
||||
|
||||
prepend header
|
||||
title #{t('account.preferences')} | #{t(config.web.t_name)}
|
||||
title #{t('account.preferences')} | #{site_name}
|
||||
|
||||
block main
|
||||
.row
|
||||
@@ -10,7 +10,7 @@ block main
|
||||
.form-group
|
||||
label.col-sm-2.col-md-offset-1.control-label= t('view.preferences.qq')
|
||||
.col-sm-5
|
||||
input.input-qq.form-control(type='text', value=account.preferences.qq)
|
||||
input.form-control(name='qq', type='text', value=account.preferences.qq)
|
||||
.form-group
|
||||
.col-sm-offset-3
|
||||
button.action-save.btn.btn-lg.btn-success(type='button')= t('common.save')
|
||||
@@ -21,7 +21,7 @@ block main
|
||||
.form-group
|
||||
label.col-sm-2.col-md-offset-1.control-label= t('view.preferences.code')
|
||||
.col-sm-5
|
||||
input.input-coupon_code.form-control(type='text')
|
||||
input.form-control(name='coupon_code', type='text')
|
||||
.form-group
|
||||
.col-sm-offset-3
|
||||
button.action-use.btn.btn-lg.btn-success(type='button')= t('common.apply')
|
||||
@@ -66,4 +66,4 @@ block main
|
||||
|
||||
|
||||
append footer
|
||||
script(src='/script/account/preferences.js')
|
||||
script(src='/script/account.js')
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
extends ../layout
|
||||
|
||||
prepend header
|
||||
title #{t('account.register')} | #{t(config.web.t_name)}
|
||||
title #{t('account.register')} | #{site_name}
|
||||
|
||||
block main
|
||||
header= t('account.register')
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
extends layout
|
||||
|
||||
prepend header
|
||||
title #{t('admin.admin_panel')} | #{t(config.web.t_name)}
|
||||
title #{t('admin.admin_panel')} | #{site_name}
|
||||
|
||||
append header
|
||||
link(rel='stylesheet', href='/style/admin.css')
|
||||
@@ -31,7 +31,7 @@ block main
|
||||
|
||||
header 付费方案
|
||||
|
||||
each plan, name in app.billing.plans
|
||||
each plan, name in app.plans
|
||||
.panel.panel-success
|
||||
.panel-heading
|
||||
strong= name
|
||||
|
||||
@@ -5,8 +5,8 @@ html
|
||||
block header
|
||||
link(rel='stylesheet', href='/bower_components/bootstrap/dist/css/bootstrap.min.css')
|
||||
link(rel='stylesheet', href='/style/layout.css')
|
||||
for hook in applyHooks('view.layout.styles')
|
||||
link(rel='stylesheet', href=hook.path)
|
||||
for path in getHooks('view.layout.styles', {pluck: 'path'})
|
||||
link(rel='stylesheet', href=path)
|
||||
|
||||
body(data-username="#{account ? account.username : ''}", data-locale-version=app.i18n.localeHash(req), data-csrf-token=req.session.csrf_token)
|
||||
header.navbar-fixed-top
|
||||
@@ -21,7 +21,7 @@ html
|
||||
a.navbar-brand(href='/')= t(config.web.t_name)
|
||||
#navbar-collapse.collapse.navbar-collapse
|
||||
ul.nav.navbar-nav
|
||||
for hook in applyHooks('view.layout.menu_bar')
|
||||
for hook in getHooks('view.layout.menu_bar')
|
||||
li
|
||||
a(href=hook.href, target=hook.target)= hook.plugin.getTranslator(req)(hook.t_body)
|
||||
ul.nav.navbar-nav.navbar-right
|
||||
@@ -85,6 +85,6 @@ html
|
||||
script(src='/bower_components/backbone/backbone.js')
|
||||
script(src='/bower_components/bootstrap/dist/js/bootstrap.min.js')
|
||||
script(src='/script/layout.js')
|
||||
for hook in applyHooks('view.layout.scripts')
|
||||
script(src=hook.path)
|
||||
for path in getHooks('view.layout.scripts', {pluck: 'path'})
|
||||
script(src=path)
|
||||
block footer
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
extends layout
|
||||
|
||||
prepend header
|
||||
title #{t('panel.')} | #{t(config.web.t_name)}
|
||||
title #{t('panel.')} | #{site_name}
|
||||
|
||||
append header
|
||||
link(rel='stylesheet', href='/style/panel.css')
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
extends ../layout
|
||||
|
||||
prepend header
|
||||
title #{t('account.financials')} | #{t(config.web.t_name)}
|
||||
title #{t('account.financials')} | #{site_name}
|
||||
|
||||
block main
|
||||
.row
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
extends ../layout
|
||||
|
||||
prepend header
|
||||
title #{t('ticket.create_ticket')} | #{t(config.web.t_name)}
|
||||
title #{t('ticket.create_ticket')} | #{site_name}
|
||||
|
||||
append header
|
||||
link(rel='stylesheet', href='/style/ticket.css')
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
extends ../layout
|
||||
|
||||
prepend header
|
||||
title #{t('ticket.ticket_list')} | #{t(config.web.t_name)}
|
||||
title #{t('ticket.ticket_list')} | #{site_name}
|
||||
|
||||
block main
|
||||
#list-view
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
extends ../layout
|
||||
|
||||
prepend header
|
||||
title #{req.ticket.title} | #{t(config.web.t_name)}
|
||||
title #{req.ticket.title} | #{site_name}
|
||||
|
||||
append header
|
||||
link(rel='stylesheet', href='/style/ticket.css')
|
||||
|
||||
@@ -18,9 +18,7 @@
|
||||
"scripts": {
|
||||
"install": "./node_modules/.bin/bower install",
|
||||
"start": "./node_modules/.bin/coffee app.coffee | ./node_modules/.bin/bunyan -o short",
|
||||
"test": "COV_TEST=true ./node_modules/.bin/mocha --compilers coffee:coffee-script/register --require test/env --reporter node_modules/mocha-reporter-cov-summary -- core/test/*.test.coffee plugins/*/test",
|
||||
"test-only": "./node_modules/.bin/mocha --compilers coffee:coffee-script/register --require test/env -b -- core/test/*.test.coffee plugins/*/test",
|
||||
"test-cov-html": "COV_TEST=true ./node_modules/.bin/mocha --compilers coffee:coffee-script/register --require test/env --reporter html-cov -- core/test/*.test.coffee plugins/*/test > coverage-reporter.html"
|
||||
"test": "COV_TEST=true ./node_modules/.bin/mocha --compilers coffee:coffee-script/register --require test/env --reporter node_modules/mocha-reporter-cov-summary -- core/test/*.test.coffee plugins/*/test"
|
||||
},
|
||||
"dependencies": {
|
||||
"async": "^0.9.0",
|
||||
@@ -42,7 +40,7 @@
|
||||
"insight": "^0.4.3",
|
||||
"jade": "^1.7.0",
|
||||
"json-stable-stringify": "^1.0.0",
|
||||
"mabolo": "^0.1.3",
|
||||
"mabolo": "^0.3.0-rc.1",
|
||||
"markdown": "^0.5.0",
|
||||
"moment-timezone": "^0.2.5",
|
||||
"mongodb": "^2.0.7",
|
||||
@@ -54,7 +52,8 @@
|
||||
"request": "^2.48.0",
|
||||
"semver": "^4.1.0",
|
||||
"ssh2": "^0.3.6",
|
||||
"underscore": "^1.7.0"
|
||||
"underscore": "^1.7.0",
|
||||
"q": "^1.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"chai": "^1.10.0",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
extends ../../../core/view/layout
|
||||
|
||||
prepend header
|
||||
title #{t('server_monitor')} | #{t(config.web.t_name)}
|
||||
title #{t('server_monitor')} | #{site_name}
|
||||
|
||||
append header
|
||||
link(rel='stylesheet', href='/plugin/linux/style/monitor.css')
|
||||
|
||||
@@ -5,13 +5,13 @@ global._ = require 'underscore'
|
||||
global.fs = require 'fs'
|
||||
|
||||
if fs.existsSync "#{__dirname}/../config.coffee"
|
||||
config = require '../config'
|
||||
global.config = require '../config'
|
||||
else
|
||||
config = require '../sample/core.config.coffee'
|
||||
global.config = require '../sample/core.config.coffee'
|
||||
|
||||
global.Q = require 'q'
|
||||
global.chai = require 'chai'
|
||||
global.async = require 'async'
|
||||
global.config = config
|
||||
global.supertest = require 'supertest'
|
||||
|
||||
if process.env.COV_TEST == 'true'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
utils = require '../core/utils'
|
||||
global.utils = require '../core/utils'
|
||||
|
||||
createAgent = (callback) ->
|
||||
agent = supertest.agent app.express
|
||||
@@ -9,6 +9,9 @@ createAgent = (callback) ->
|
||||
agent: agent
|
||||
csrf_token: res.body.csrf_token
|
||||
|
||||
cleanUpByAccount = ({account_id}, callback) ->
|
||||
app.models.Account.findByIdAndRemove account_id, callback
|
||||
|
||||
createLoggedAgent = (callback) ->
|
||||
createAgent (err, {agent, csrf_token}) ->
|
||||
username = 'test' + utils.randomString(8).toLowerCase()
|
||||
@@ -22,6 +25,9 @@ createLoggedAgent = (callback) ->
|
||||
email: email
|
||||
password: password
|
||||
.end (err, res) ->
|
||||
after (done) ->
|
||||
cleanUpByAccount res.body.account_id, done
|
||||
|
||||
callback err,
|
||||
agent: agent
|
||||
username: username
|
||||
@@ -32,4 +38,5 @@ createLoggedAgent = (callback) ->
|
||||
|
||||
_.extend global,
|
||||
createAgent: createAgent
|
||||
cleanUpByAccount: cleanUpByAccount
|
||||
createLoggedAgent: createLoggedAgent
|
||||
|
||||
Reference in New Issue
Block a user