refactor ticket

This commit is contained in:
jysperm
2014-09-22 01:34:10 +08:00
parent dc46373589
commit 6fc8098f8d
11 changed files with 105 additions and 211 deletions

View File

@@ -49,8 +49,13 @@ exports.run = ->
app.billing = require './core/billing'
app.pluggable = require './core/pluggable'
app.middleware = require './core/middleware'
app.notification = require './core/notification'
app.authenticator = require './core/authenticator'
app.template_data =
ticket_create_email: fs.readFileSync('./core/template/ticket_create_email.html').toString()
ticket_reply_email: fs.readFileSync('./core/template/ticket_reply_email.html').toString()
app.use connect.json()
app.use connect.urlencoded()
app.use connect.cookieParser()
@@ -70,7 +75,8 @@ exports.run = ->
language = req.cookies.language ? config.i18n.default_language
timezone = req.cookies.timezone ? config.i18n.default_timezone
res.locals.moment = res.moment = moment().locale(language).tz(timezone)
res.locals.moment = res.moment = ->
return moment.apply(@, arguments).locale(language).tz(timezone)
next()

View File

@@ -1,3 +1,4 @@
{ObjectID} = require 'mongodb'
_ = require 'underscore'
mAccount = require './model/account'
@@ -36,6 +37,10 @@ exports.renderAccount = (req, res, next) ->
old_render = res.render
res.render = (name, options = {} , fn) ->
options = _.extend {account: req.account}, options
options.inGroup = (group_name) ->
return group_name in options.account?.groups
old_render.call res, name, options, fn
next()
@@ -74,17 +79,3 @@ exports.constructObjectID = (fields = ['id']) ->
req.body[field] = new ObjectID req.body[field]
next()
exports.loadTicket = (req, res, next) ->
req.inject [exports.requireAuthenticate, exports.constructObjectID()], ->
mTicket.findOne _id: req.body.id, (err, ticket) ->
unless ticket
return res.error 'ticket_not_exist'
unless mTicket.getMember ticket, req.account
unless 'root' in req.account.groups
return res.error 'forbidden'
req.ticket = ticket
next()

View File

@@ -1,4 +1,6 @@
{ObjectID} = require 'mongodb'
async = require 'async'
_ = require 'underscore'
module.exports = exports = app.db.collection 'notifications'

View File

@@ -1,5 +1,6 @@
{ObjectID} = require 'mongodb'
{markdown} = require 'markdown'
_ = require 'underscore'
module.exports = exports = app.db.collection 'tickets'
@@ -14,8 +15,7 @@ sample =
content_html: 'Ticket Conetnt(HTML)'
status: 'open/pending/finish/closed'
payload:
public: false
options: {}
members: [
ObjectID()
@@ -27,10 +27,10 @@ sample =
created_at: Date()
content: 'Reply Content(Markdown)'
content_html: 'Reply Conetnt(HTML)'
payload: {}
options: {}
]
exports.createTicket = (account, title, content, members, status, payload, callback) ->
exports.createTicket = (account, title, content, members, status, options, callback) ->
exports.insert
account_id: account._id
created_at: new Date()
@@ -40,7 +40,7 @@ exports.createTicket = (account, title, content, members, status, payload, callb
content_html: markdown.toHTML content
status: status
members: _.pluck members, '_id'
payload: payload
options: options
replies: []
, (err, result) ->
callback _.first result
@@ -52,7 +52,7 @@ exports.createReply = (ticket, account, content, status, callback) ->
created_at: new Date()
content: content
content_html: markdown.toHTML content
payload: {}
options: {}
exports.update {_id: ticket._id},
$push:

View File

@@ -1,10 +1,12 @@
async = require 'async'
mAccount = require './model/account'
mNotification = require './model/notification'
i18n = require './i18n'
config = require './config'
config = require '../config'
{NOTICE, EVENT, LOG} = exports =
{NOTICE, EVENT, LOG} = module.exports = exports =
NOTICE: 'notice'
EVENT: 'event'
LOG: 'log'

View File

@@ -2,12 +2,26 @@ express = require 'express'
async = require 'async'
_ = require 'underscore'
{requireAuthenticate, renderAccount, getParam, loadTicket} = app.middleware
{requireAuthenticate, renderAccount, getParam, constructObjectID} = app.middleware
{mAccount, mTicket} = app.models
{config, notification} = app
module.exports = exports = express.Router()
loadTicket = (req, res, next) ->
req.inject [requireAuthenticate, constructObjectID()], ->
mTicket.findOne _id: req.body.id, (err, ticket) ->
unless ticket
return res.error 'ticket_not_exist'
unless mTicket.getMember ticket, req.account
unless 'root' in req.account.groups
return res.error 'forbidden'
req.ticket = ticket
next()
exports.get '/list', requireAuthenticate, renderAccount, (req, res) ->
mTicket.find
$or: [
@@ -44,6 +58,8 @@ exports.get '/view', renderAccount, getParam, loadTicket, (req, res) ->
mAccount.findOne _id: ticket.account_id, (err, account) ->
ticket.account = account
console.log ticket.created_at
res.render 'ticket/view',
ticket: ticket
@@ -53,46 +69,27 @@ exports.post '/create', requireAuthenticate, (req, res) ->
is_admin = 'root' in req.account.groups
createTicket = (members, status, callback) ->
mTicket.createTicket req.account, req.body.title, req.body.content, members, status, {}, (err, ticket) ->
res.json
id: ticket._id
callback ticket
mTicket.createTicket req.account, req.body.title, req.body.content, [req.account], 'pending', {}, (ticket) ->
res.json
id: ticket._id
if is_admin
req.body.members ?= []
async.each req.body.members, (member_name, callback) ->
mAccount.byUsernameOrEmailOrId member_name, (err, member) ->
if member
callback null, member
else
callback member_name
, (err, result) ->
if err
return res.error 'invalid_account', username: err
else
unless _.find(result, (item) -> item._id == req.account._id)
result.push req.account
createTicket result, 'open'
else
createTicket [req.account], 'pending', (ticket) ->
notification.createGroupNotice 'root', 'ticket_create',
title: _.template res.t('notification_title.ticket_create'), ticket
body: _.template fs.readSync('./../template/ticket_create_email.html'),
ticket: ticket
account: req.account
config: config
, ->
notification.createGroupNotice 'root', 'ticket_create',
title: _.template res.t('notification_title.ticket_create'), ticket
body: _.template app.template_data['ticket_create_email'],
ticket: ticket
account: req.account
config: config
, ->
exports.post '/reply', loadTicket, (req, res) ->
{ticket} = req
unless req.body.content
return res.error 'invalid_content'
status = if 'root' in req.account.groups then 'open' else 'pending'
mTicket.createReply ticket, req.account, req.body.content, status, (err, reply) ->
mTicket.createReply ticket, req.account, req.body.content, status, (reply) ->
async.each ticket.members, (member_id, callback) ->
if member_id.toString() == req.account._id.toString()
return callback()
@@ -102,7 +99,7 @@ exports.post '/reply', loadTicket, (req, res) ->
, (err, account) ->
notification.createNotice account, 'ticket_reply',
title: _.template res.t('notification_title.ticket_create'), ticket
body: _.template fs.readSync('./../template/ticket_create_email.html'),
body: _.template app.template_data['ticket_reply_email'],
ticket: ticket
reply: reply
account: req.account
@@ -114,115 +111,24 @@ exports.post '/reply', loadTicket, (req, res) ->
return res.json
id: reply._id
exports.post '/list', requireAuthenticate, (req, res) ->
mTicket.find do ->
selector =
$or: [
account_id: req.account._id
,
members: req.account._id
]
if req.body.status?.toLowerCase() in ['open', 'pending', 'finish', 'closed']
selector['status'] = req.body.status.toLowerCase()
return selector
,
sort:
updated_at: -1
limit: req.body.limit ? 30
skip: req.body.skip ? 0
.toArray (err, tickets) ->
res.json _.map tickets, (item) ->
return {
id: item._id
title: item.title
status: item.status
updated_at: item.updated_at
}
exports.post '/update', loadTicket, (req, res) ->
exports.post '/update_status', loadTicket, (req, res) ->
{ticket} = req
modifier = {}
addToSetModifier = []
pullModifier = []
if req.body.status
if 'root' in req.account.groups
allow_status = ['open', 'pending', 'finish', 'closed']
else
allow_status = ['closed']
if req.body.status in allow_status
if ticket.status == req.body.status
return res.error 'already_in_status'
else
modifier['status'] = req.body.status
else
return res.error 'invalid_status'
callback = ->
async.parallel [
(callback) ->
unless _.isEmpty modifier
mTicket.update {_id: ticket._id},
$set: modifier
, callback
else
callback()
(callback) ->
unless _.isEmpty addToSetModifier
mTicket.update {_id: ticket._id},
$addToSet:
members:
$each: addToSetModifier
, callback
else
callback()
(callback) ->
unless _.isEmpty pullModifier
mTicket.update {_id: ticket._id},
$pullAll:
members: pullModifier
, callback
else
callback()
], ->
return res.json {}
if 'root' in req.account.groups
if req.body.payload
if req.body.payload.public
modifier['payload.public'] = true
else
modifier['payload.public'] = false
if req.body.members
tasks = {}
member_name = _.filter _.union(req.body.members.add, req.body.members.remove), (item) -> item
for item in member_name
tasks[item] = do (item = _.clone(item)) ->
return (callback) ->
mAccount.byUsernameOrEmailOrId item, (result) ->
callback null, result
async.parallel tasks, (err, result) ->
if req.body.members.add
for item in req.body.members.add
addToSetModifier.push result[item]._id
if req.body.members.remove
for item in req.body.members.remove
pullModifier.push result[item]._id
callback()
else
callback()
allow_status = ['open', 'pending', 'finish', 'closed']
else
callback()
allow_status = ['closed']
if req.body.status in allow_status
if ticket.status == req.body.status
return res.error 'already_in_status'
else
modifier['status'] = req.body.status
else
return res.error 'invalid_status'
mTicket.update _id: ticket._id
$set:
status: req.body.status
, ->
res.json {}

View File

@@ -1,13 +1,7 @@
$ ->
$('.action-create').click ->
$.post '/ticket/create/', JSON.stringify
type: $('#type').val()
title: $('#title').val()
content: $(':input[name=content]').val()
.fail (jqXHR) ->
if jqXHR.responseJSON?.error
alert jqXHR.responseJSON.error
else
alert jqXHR.statusText
.success (data, text_status, jqXHR) ->
location.href = "/ticket/view/?id=#{jqXHR.responseJSON.id}"
request '/ticket/create/',
title: $('.input-title').val()
content: $('.input-content').val()
, (result) ->
location.href = "/ticket/view/?id=#{result.id}"

View File

@@ -2,25 +2,15 @@ $ ->
id = $('.row.content').data 'id'
$('.action-reply').click ->
$.post '/ticket/reply/', JSON.stringify
request '/ticket/reply/',
id: id
content: $('#reply-content').val()
.fail (jqXHR) ->
if jqXHR.responseJSON?.error
alert jqXHR.responseJSON.error
else
alert jqXHR.statusText
.success ->
content: $('.input-content').val()
, (result) ->
location.reload()
$('.change-status').click ->
$.post '/ticket/update/', JSON.stringify
$('.action-update-status').click ->
request '/ticket/update_status/',
id: id
status: $(@).data 'status'
.fail (jqXHR) ->
if jqXHR.responseJSON?.error
alert jqXHR.responseJSON.error
else
alert jqXHR.statusText
.done ->
, (result) ->
location.reload()

View File

@@ -8,11 +8,11 @@ append header
block main
header= t('ticket.create_ticket')
form.form-horizontal(method='post', role='form')
form.form-horizontal
.form-group.padding
input#title.form-control(type='text', name='title', placeholder= t('ticket.title'),required)
input.input-title.form-control(type='text', placeholder= t('ticket.title'))
.form-group.padding
textarea.form-control(name='content', rows='15', required)
textarea.input-content.form-control(rows='15')
.form-group.padding
button.btn.btn-lg.btn-primary.action-create(type='button')= t('ticket.create')

View File

@@ -7,7 +7,7 @@ append header
link(rel='stylesheet', href='/style/ticket.css')
block main
.row.content(data-id= '#{ticket._id}')
.row.content(data-id='#{ticket._id}')
header
| #{ticket.title}  
if ticket.status == 'closed'
@@ -23,11 +23,12 @@ block main
.row
header= t('ticket.replies')
ul.list-group
for reply in ticket.replys
for reply in ticket.replies
li.list-group-item.clearfix
a.pull-left
img(src= reply.account.setting.avatar_url)
img(src= reply.account.settings.avatar_url)
.list-content
p!= reply.content_html
p
@@ -37,20 +38,22 @@ block main
.row
if ticket.status != 'closed'
header= t('ticket.create_reply')
form.form-horizontal(method='post', role='form')
form.form-horizontal
if ticket.status != 'closed'
.form-group.padding
textarea.form-control#reply-content(name='content', rows='5', required)
textarea.form-control.input-content(rows='5')
.form-group.padding
if ticket.status == 'closed'
button(disabled).btn.btn-lg.btn-primary 已关闭
else
button.btn.btn-lg.btn-primary.action-reply(type='button')= t('ticket.create_reply')
button(type='button', data-status='closed').btn.btn-lg.btn-danger.change-status= t('ticket.close_ticket')
if mAccount.inGroup(account, 'root') && (ticket.status == 'open' || ticket.status == 'pending')
button(type='button', data-status='finish').btn.btn-lg.btn-success.change-status= t('ticket.finish_ticket')
if mAccount.inGroup(account, 'root') && ticket.status == 'closed'
button(type='button', data-status='open').btn.btn-lg.btn-success.change-status= t('ticket.reopen_ticket')
button(type='button', data-status='closed').btn.btn-lg.btn-danger.action-update-status= t('ticket.close_ticket')
if inGroup('root') && (ticket.status == 'open' || ticket.status == 'pending')
button(type='button', data-status='finish').btn.btn-lg.btn-success.action-update-status= t('ticket.finish_ticket')
if inGroup('root') && ticket.status == 'closed'
button(type='button', data-status='open').btn.btn-lg.btn-success.action-update-status= t('ticket.reopen_ticket')
prepend sidebar
.row
@@ -60,17 +63,17 @@ prepend sidebar
header= t('ticket.creator')
li.list-group-item.clearfix
a.pull-left
img(src= ticket.account.setting.avatar_url)
img(src= ticket.account.settings.avatar_url)
p
span.label.label-info= ticket.account.username
br
span.label.label-default(title= ticket.created_at)= moment(ticket.created_at).fromNow()
span.label.label-default(title=ticket.created_at)= moment(ticket.created_at).fromNow()
.row
header= t('ticket.members')
for member in ticket.members
a.pull-left
img(src= member.setting.avatar_url, alt= member.username)
img(src= member.settings.avatar_url, alt= member.username)
append footer
script(src='/script/ticket/view.js')

View File

@@ -30,8 +30,8 @@
"coffee-script": "^1.7.1",
"connect": "^2.17.3",
"express": "^4.8.4",
"harp": "~0.13.0",
"jade": "^1.3",
"harp": "^0.13.0",
"jade": "^1.3.1",
"markdown": "^0.5.0",
"middleware-injector": "^0.1.1",
"moment-timezone": "^0.2.2",