mirror of
https://github.com/HackPlan/RootPanel.git
synced 2026-01-12 22:27:09 +08:00
refactor ticket
This commit is contained in:
@@ -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()
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
{ObjectID} = require 'mongodb'
|
||||
async = require 'async'
|
||||
_ = require 'underscore'
|
||||
|
||||
module.exports = exports = app.db.collection 'notifications'
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
@@ -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}"
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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')
|
||||
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user