# Source: server.js

    'use strict';
    let express = require('express');
    // let expressRest = require('express-rest');
    // let config = require('conf-env');
    let bodyParser = require('body-parser');
    let url = require('url');
    let http = require('http');
    let _ = require('lodash');
    require('env-deploy')(__dirname);
    let ObjectId = require('mongodb').ObjectId;
    let app = express();
    let Agenda = require('agenda');
    let processname = (process.getuid) ? process.getuid() : 'windozdevbox';
    let agenda = new Agenda({name: processname})
        .database(process.env.MONGOSTRING);

    app.use(bodyParser.json());
    app.use((req, res, next) => {
      res.removeHeader('X-Powered-By');
      next();
    });
    /**
     * let's get our dashboard served (on the same instance now because lazy)
     */

    /**
     * development error handler, will print stacktrace
     * @param  {Callback} app.get('env' [description]
     * @return {[type]}               [description]
     */
    if (app.get('env') === 'development') {
      app.use(function(err, req, res, next) {
        res.status(err.status || 500);
        res.render('error', {
          message: err.message,
          error: err
        });
      });
    }

    /**
     * production error handler, no stacktraces leaked to user
     * @param  {Callback} function(err, req, res, next [description]
     * @return {[type]}               [description]
     */
    app.use(function(err, req, res, next) {
      res.status(err.status || 500);
      res.render('error', {
        message: err.message,
        error: {}
      });
    });

    /**
     * generateResponseObj
     * wrap the request with metadata and return an object to send back
     * @param  {Object} jerb    request Object describing Job
     * @param  {String} reqtime time stamp of request
     * @return {String}         JSON Object as a string
     */
    let generateResponseObj = function(jerb, reqtime = new Date().toISOString()) {
      let time = reqtime;
      // let dataitems = jerb.keys();
      /**
       * omit core request keys from the arbitrary data we'll make available under req.data for the object we forwarard on
       * to the callback url
       */
      // console.log('jerb', JSON.stringify(jerb.attrs.data, null, 1));
      let data = _.omit(jerb.attrs.data, 'when', 'url', 'name');
      // console.log('data', JSON.stringify(data, null, 1));
      return JSON.stringify({
        meta: {
          time: time,
          request: jerb
        },
        data: data
      });
    };

    /**
     * Create Job
     * @param  {String} '/'           path
     * @param  {Callback}
     * @return {[type]}               [description]
     */
    app.post('/', function(req, res, next) {
      let reqtime = new Date().toISOString();
      let jerb = req.body;
      let opts = {};
      /**
       * process options
       */
      if (jerb.concurrency !== undefined) {
        opts.concurrency = jerb.concurrency;
      }
      if (jerb.lockLimit !== undefined) {
        opts.lockLimit = jerb.lockLimit;
      }
      if (jerb.priority !== undefined) {
        opts.priority = jerb.priority;
      }
      if (jerb.lockLifetime !== undefined) {
        opts.lockLifetime = jerb.lockLifetime;
      }

      console.log('URL ' + req.body.url);

      /**
       * create job
       * @param  {String} jerb.name [description]
       * @param  {Object} opts      [description]
       * @param  {Callback} (jorb, done          [description]
       * @return {[type]}           [description]
       */
      agenda.define(jerb.name, opts, (jorb, done) => {
        let postData = JSON.stringify(jerb);
        let cburl = url.parse(jerb.url, true, true);
        let options = {
          hostname: cburl.hostname,
          port: cburl.port,
          path: cburl.path,
          method: 'POST',
          headers: {
            'Content-Type': 'application/json; charset=utf-8',
            'Content-Length': postData.length
          }
        };

        let req = http.request(options, res => {
          console.log(`STATUS: ${res.statusCode}`);
          console.log(`HEADERS: ${JSON.stringify(res.headers)}`);
          res.setEncoding('utf8');
          res.on('data', chunk => {
            console.log(`BODY: ${chunk}`);
          });
          res.on('end', () => {
            console.log('No more data in response.');
          });
        });

        req.on('error', err => {
          console.log(`problem with request: ${err.message}`);
        });

        req.write(postData);
        req.end();
        console.log('done with ' + jerb.name);
        done();
      });

      /**
       * Schedules a job to run name once at a given time. when can be a Date or a
       * String such as tomorrow at 5pm.
       * @param  {Date|String} jerb.when Date or String describing when the job should be run
       * @param  {String} jerb.name Friendly name for Job
       * @param  {Object} jerb      [description]
       * @param  {Callback} (err,     jerb          [description]
       * @return {[type]}           [description]
       */
      agenda.schedule(jerb.when, jerb.name, jerb, (err, jerb) => {
        if (err) {
          res.status(400)
             .send({error: {code: 400, message: 'we were unable to process your reqest'}});
          return next(err);
        }
        res.status(201)
           .set('Content-Type', 'application/json; charset=utf-8')
           .send(generateResponseObj(jerb, reqtime));
      });

      agenda.start();
    });

    /**
     * Get Job by jobid
     * @param  {String} '/:jobid' path with jobid paramter
     * @param  {Callback} (req,     res,          next [description]
     * @return {[type]}           [description]
     */
    app.get('/:jobid', (req, res, next) => {
      agenda.jobs({_id: ObjectId(req.params.jobid)}, (err, job) => {
        if (err) {
          res.status(400)
             .send({error: {code: 400, message: 'we were unable to process your reqest'}});
          return next(err);
        }
        console.log(job, 'job');
        let resp = job[0];
        res.status(200)
           .set('Content-Type', 'application/json; charset=utf-8')
           .send(generateResponseObj(resp));
      });
    });

    /**
     * Update Job by jobid
     * @param  {[type]} '/:jobid' [description]
     * @param  {[type]} (req,     res,          next [description]
     * @return {[type]}           [description]
     */
    app.put('/:jobid', (req, res, next) => {
      let jerb = req.body;
      let resp = '';
      console.log(req.params.jobid, '_id');

      if (!ObjectId(req.params.jobid)) {
        let err = 'we were unable to process your reqest';
        res.status(400)
           .send({error: {code: 400, message: err}});
        console.warn('something something something darkside');
        return next(err);
      }

      /**
       * [jobs description]
       * @param  {[type]} {_id: ObjectId(req.params.jobid [description]
       * @return {[type]}       [description]
       */
      agenda.jobs({_id: ObjectId(req.params.jobid)}, (err, job) => {
        if (err) {
          console.warn(err);
          res.status(400)
             .send({error: {code: 400, message: 'we were unable to process your reqest'}})
             .next();
          return next(err);
        } else if (job[0] === undefined) {
          console.warn('you are a dumb fuck');
          res.status(400)
             .send({error: {code: 400, message: 'we were unable to process your reqest'}})
             .next();
          return next(err);
        } else {
          console.log(job.length, job);
          job = job[0];
          console.log('fuckfuckfuck ', job);
          // console.dir(job.agenda.attrs, {depth: null, colors: true});
          console.dir(jerb, {depth: null, colors: true});

          job.attrs = Object.assign(job.attrs, jerb);
          job.attrs.data = Object.assign(job.attrs.data, jerb);
          job.save(err => {
            if (err) {
              res.status(400)
                 .send({error: {code: 400, message: 'we were unable to process your reqest'}});
              console.warn(err);
            } else {
              console.log('Successfully saved Job ' + job.attrs.name);
              resp = JSON.stringify(job);
              res.status(200)
                 .set('Content-Type', 'application/json; charset=utf-8')
                 .send(generateResponseObj(resp));
            }
          });
        }
      });
    });

    /**
     * Remove Job by jobid
     * @param  {[type]} '/:jobid' [description]
     * @param  {[type]} (req,     res,          next [description]
     * @return {[type]}           [description]
     */
    app.delete('/:jobid', (req, res, next) => {
      agenda.cancel({_id: ObjectId(req.params.jobid)}, (err, numRemoved) => {
        if (err) {
          res.status(400)
             .send({error: {code: 400, message: 'we were unable to process your reqest'}});
          console.warn(err);
          return next(err);
        }
        res.status(200)
           .set('Content-Type', 'application/json; charset=utf-8')
           .send({message: 'Successfully removed %s Job(s) %s'}, numRemoved, req.params.jobid);
      });
    });

    /**
     * endpoint to use as target for tests
     * @param  {String} '/testcburl' path
     * @param  {Callback} (req,        res,          next [description]
     * @return {JSON}              same as reqest object
     */
    app.post('/testcburl', (req, res, next) => {
      if (!req.body) {
        let err = 'we were unable to process your request';
        res.status(400)
           .send({error: {code: 400, message: err}});
        return next(err);
      }
      let resp = JSON.stringify(req.body);
      console.log('TEST CB URL: ' + new Date().toISOString(), resp);
      res.status(202)
         .set('Content-Type', 'application/json; charset=utf-8')
         .send(resp);
    });

    /**
     * Ready Event handler
     * @param  {String} 'ready' Event
     * @param  {Callback}        Start processing jobs
     * @return {[type]}         n/a
     */
    agenda.on('ready', () => {
      agenda.start();
    });

    /**
     * Start Event handler
     * @param  {String} 'start' Event
     * @param  {[type]} job     =>            {  console.info('Job %s starting', job.attrs.name [description]
     * @return {stdout}         Message to stdout about which job has been started
     */
    agenda.on('start', job => {
      console.info('Job %s starting', job.attrs.name);
    });

    let server = app.listen(3000, function() {
      let host = server.address().address;
      host = (host === '::' ? 'localhost' : host);
      let port = server.address().port;

      console.info('listening at http://%s:%s', host, port);
    });

    /**
     *
     */
    /**
     * Graceful shutdown
     * @return {[type]} [description]
     */
    function graceful() {
      agenda.stop(() => {
        process.exit(0);
      });
    }

    process.on('SIGTERM', graceful);
    process.on('SIGINT', graceful);