UNPKG

ticketman

Version:

A simple pull-based job/ticket system contians a centeral ticket dispatcher and distributed workers. This system is written in NodeJS, runing on MongoDB

313 lines (245 loc) 7.47 kB
mongoose = require('mongoose') Ticket = mongoose.model('Ticket') crypto = require 'crypto' STATUS = require "../enums/ticket_status" MAX_ATTEMPTS_BEFORE_ABANDON = 16 MAX_TIME_ALLOWED_FOR_PROCESSING = 1000 * 60 * 60 debuglog = require("debug")("ticketman:controller:ticket") # list tickets # GET / # GET /tickets exports.index = (req, res, next)-> debuglog "index" res.render 'tickets/index', title: 'All Tickets' tickets : [] return exports.list = (req, res, next)-> debuglog "list req.query: %j", req.query query = Ticket.paginate(req.query || {}, '_id').select('-comments -content') if req.query.status? query.where status : req.query.status query.execPagination (err, result)-> return next err if err? result.success = true console.log "[ticket::list] dump result:" console.dir result res.json result return exports.count = (req, res, next)-> result = {} Ticket.count (err, count)-> next err if err? result.all = count Ticket.count {status: STATUS.PENDING}, (err, count)-> next err if err? result[STATUS.PENDING] = count Ticket.count {status: STATUS.PROCESSING}, (err, count)-> next err if err? result[STATUS.PROCESSING] = count Ticket.count {status: STATUS.COMPLETE}, (err, count)-> next err if err? result[STATUS.COMPLETE] = count Ticket.count {status: STATUS.ABANDON}, (err, count)-> next err if err? result[STATUS.ABANDON] = count res.json result return return return return return return # GET /tickets/:id exports.show = (req, res, next)-> debuglog "show" id = String(req.params.id || '') return next() unless id? Ticket.findById id, (err, ticket)-> return next err if err? res.render 'tickets/show', title: 'All Tickets' ticket : ticket return return # POST /api/tickets/new exports.create = (req, res, next)-> debuglog "create" title = (req.body||{}).title #md5 = crypto.createHash('md5') #md5.update(title||'') #req.body.token = md5.digest('hex') req.body.token = crypto.createHash('md5').update(title).digest('hex').toLowerCase() ticket = new Ticket(req.body) ticket.save (err)=> if err? return res.json success : false error : err.toString() else return res.json success : true ticket : ticket return # PUT /api/tickets/assign exports.assign = (req, res, next)-> debuglog "assign, req.worker:%j", req.worker req.body.worker = req.worker.name Ticket.arrangeAssignment req.body, (err, ticket) -> return next(err) if err? unless ticket? return res.json success : false error : "no pending ticket of #{req.body.category}" # clear comments when assign ticket ticket.comments = [] #console.log "!!!! before assign" #console.dir ticket #ticket = JSON.stringify(ticket) #ticket = JSON.parse(ticket) return res.json success : true ticket : ticket return # PUT /api/tickets/:id/comment exports.comment = (req, res, next)-> id = req.params.id || '' return next() unless id? req.body.name = req.worker.name Ticket.addComment id, req.body, (err, ticket)-> return next(err) if err? unless ticket? return res.json success : false error : "no commented ticket of #{id}" return res.json success : true ticket : ticket return # PUT /api/tickets/:id/complete exports.complete = (req, res, next)-> id = String(req.params.id || '') return next() unless id? req.body.id = id Ticket.changeStatus req.body, STATUS.COMPLETE, (err, ticket)-> return next(err) if err? return next() unless ticket? return res.json ticket return # PUT /api/tickets/:id/giveup exports.giveup = (req, res, next)-> id = String(req.params.id || '') return next() unless id? req.body.id = id comment = name: req.body.name || req.worker.name kind: "danger" content : req.body.reason || "#{req.worker.name} fail to process this ticket" Ticket.addComment id, comment, (err, ticket)-> return next(err) if err? unless ticket? return res.json success : false error : "missing ticket of #{id}" # abandon ticket if exceed max attempts targetStatus = if ticket.attempts < MAX_ATTEMPTS_BEFORE_ABANDON then STATUS.PENDING else STATUS.ABANDON Ticket.changeStatus req.body, targetStatus, (err, ticket)-> return next(err) if err? return next() unless ticket? ticket.update {$inc: {attempts:1}}, (err, numberAffected)-> return next(err) if err? ticket.attempts = numberAffected return res.json ticket return return return # PUT /tickets/:id/abandon exports.abandon = (req, res, next)-> id = String(req.params.id || '') return next() unless id? Ticket.findById id, (err, ticket)-> return next err if err? return next() unless ticket? return next(new Error "only pending ticket could be abandoned") unless ticket.status is STATUS.PENDING Ticket.changeStatus {id : ticket.id}, STATUS.ABANDON, (err, ticket)-> return next err if err? return next() unless ticket? return res.redirect "/tickets" return return # PUT /tickets/:id/comment exports.adminComment = (req, res, next)-> id = String(req.params.id || '') return next() unless id? req.body.content = req.body.content.trim() console.log "[ticket::==========] req.body.content:#{req.body.content}" return next(new Error "please say something") unless req.body.content req.body.kind = "warning" req.body.name = "admin" Ticket.addComment id, req.body, (err, ticket)-> return next(err) if err? return next() unless ticket? return res.redirect "/tickets/#{id}" return #GET /tickets/:token/status exports.showStatus = (req, res, next) -> token = req.params.token unless token res.json "success":false "error": "missing param token" return query = { token: token } Ticket.findOne query, (err, ticket) -> if err? res.json "success":false "error": "#{err}" return data = "success": true if ticket? data['result'] = id: ticket.id status: ticket.status else data['result'] = null return res.json data return # routine: clean up overtime processing tickets setInterval ()-> #debuglog "clean up overtime processing tickets" query = $and: [ {status : STATUS.PROCESSING} {updated_at : $lt : Date.now() - MAX_TIME_ALLOWED_FOR_PROCESSING} ] Ticket.findOne query, (err, ticket)-> if err? console.error "ERROR [ticket::interval::cleanup] error:#{err}" return unless ticket? #debuglog "no ticket" return #debuglog "[interval::cleanup] ticket:" #console.dir ticket if ticket.attempts < MAX_ATTEMPTS_BEFORE_ABANDON content = "ticket processing overtime, set back to retry." targetStatus = STATUS.PENDING else content = "ticket exceeds max attemption, so abandon" targetStatus = STATUS.ABANDON ticket.comments.push name : "admin" kind : "danger" content : content date : Date.now() ticket.status = targetStatus ticket.save (err)-> #debuglog "change to ticket applied" return return , 2000