diff --git a/app.coffee b/app.coffee index 0188d5a..c94b6aa 100644 --- a/app.coffee +++ b/app.coffee @@ -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() diff --git a/core/middleware.coffee b/core/middleware.coffee index dadd9ea..a553fd1 100644 --- a/core/middleware.coffee +++ b/core/middleware.coffee @@ -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() diff --git a/core/model/notification.coffee b/core/model/notification.coffee index 68c82bf..24d4f81 100644 --- a/core/model/notification.coffee +++ b/core/model/notification.coffee @@ -1,4 +1,6 @@ {ObjectID} = require 'mongodb' +async = require 'async' +_ = require 'underscore' module.exports = exports = app.db.collection 'notifications' diff --git a/core/model/ticket.coffee b/core/model/ticket.coffee index a2f896a..466e26c 100644 --- a/core/model/ticket.coffee +++ b/core/model/ticket.coffee @@ -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: diff --git a/core/notification.coffee b/core/notification.coffee index 0f7fcf9..b16904d 100644 --- a/core/notification.coffee +++ b/core/notification.coffee @@ -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' diff --git a/core/router/ticket.coffee b/core/router/ticket.coffee index 3f29ab8..4432b41 100644 --- a/core/router/ticket.coffee +++ b/core/router/ticket.coffee @@ -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 {} diff --git a/core/static/script/ticket/create.coffee b/core/static/script/ticket/create.coffee index 50f941b..c88fe31 100644 --- a/core/static/script/ticket/create.coffee +++ b/core/static/script/ticket/create.coffee @@ -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}" diff --git a/core/static/script/ticket/view.coffee b/core/static/script/ticket/view.coffee index 1c2eb1e..5536eda 100644 --- a/core/static/script/ticket/view.coffee +++ b/core/static/script/ticket/view.coffee @@ -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() diff --git a/core/view/ticket/create.jade b/core/view/ticket/create.jade index 85f3250..bd5df0b 100644 --- a/core/view/ticket/create.jade +++ b/core/view/ticket/create.jade @@ -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') diff --git a/core/view/ticket/view.jade b/core/view/ticket/view.jade index 7ca4f54..96aaa91 100644 --- a/core/view/ticket/view.jade +++ b/core/view/ticket/view.jade @@ -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') diff --git a/package.json b/package.json index 9cd61a9..0edf857 100644 --- a/package.json +++ b/package.json @@ -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",