"use strict";
var PRODUCTION = process.env.NODE_ENV === "production";
var CACHE_KEY = Date.now();
var STARTED = Date.now();
var VERSION = "loading";
var HOSTNAME = require("os").hostname();
if (!PRODUCTION) {
// Use environment variable to set the Bluebird long stack traces in order
// to enable it from libraries too
process.env.BLUEBIRD_DEBUG = "true";
}
require("./db");
var Promise = require("bluebird");
var express = require("express");
var Server = require("http").Server;
var prettyMs = require("pretty-ms");
var exec = require("child_process").exec;
var crypto = require("crypto");
var debug = require("debug")("app:live");
var debugMem = require("debug")("app:memory");
var serveStatic = require("serve-static");
var bodyParser = require("body-parser");
var jwtsso = require("jwtsso");
var cookieParser = require("cookie-parser");
var session = require("express-session");
var RedisStore = require("connect-redis")(session);
var User = require("./models/server/User");
var Ticket = require("./models/server/Ticket");
var config = require("./config");
/**
* http://expressjs.com/4x/api.html#req.params
*
* @namespace server
* @class Request
*/
/**
* http://expressjs.com/4x/api.html#res.status
*
* @namespace server
* @class Response
*/
var app = express();
var server = Server(app);
var sio = require("socket.io")(server);
app.sio = sio;
var sessionMiddleware = session({
name: "puavo-ticket-sid",
resave: true,
saveUninitialized: true,
store: new RedisStore(config.redis),
secret: config.sessionSecret
});
sio.use(function(socket, next) {
sessionMiddleware(socket.request, socket.request.res, next);
});
sio.use(function(socket, next) {
var req = socket.request;
Promise.resolve().then(function() {
return User.byExternalId(req.session.jwt.id).fetch({ require: true });
})
.then(function(user) {
socket.user = user;
debug("%s authenticated with socket.io", user);
})
.then(next.bind(this, null))
.catch(function(err) {
console.error("Socket.IO socket init failed", err);
next(err);
});
});
sio.sockets.on("connection", function(socket) {
socket.join(socket.user.getSocketIORoom());
socket.on("startWatching", function(ob) {
Ticket.fetchByIdConstrained(socket.user, ob.ticketId)
.then(function(ticket) {
socket.join(ticket.getSocketIORoom());
debug(
"%s started watching ticket %s",
socket.user, ticket.get("id")
);
})
.catch(console.error);
});
socket.on("stopWatching", function(ob) {
socket.leave("ticket:" + ob.ticketId);
debug(
"%s stopped watching ticket %s",
socket.user, ob.ticketId
);
});
socket.on("disconnect", function() {
debug(
"%s disconnected from socket.io",
socket.user
);
});
});
app.use(require("./utils/middleware/createSlowInternet")());
app.use(require("./utils/middleware/createResponseLogger")());
app.use(bodyParser());
app.use(cookieParser());
app.use(sessionMiddleware);
app.use(jwtsso({
// Service endpoint that issues the jwt tokens
authEndpoint: config.puavo.restServerAddress + "/v3/sso",
// Shared secret string with the above service
sharedSecret: config.puavo.sharedSecret,
// Set max age in seconds for the tokens
// Defaults to 60 seconds
maxAge: 120,
hook: function(token, done) {
User.ensureUserFromJWTToken(token)
.then(done.bind(this, null))
.catch(done);
}
}));
app.use("/styles", serveStatic(__dirname + "/styles"));
app.use("/components", serveStatic(__dirname + "/components"));
app.use("/bootstrap", serveStatic(__dirname + "/node_modules/bootstrap"));
app.use("/font-awesome", serveStatic(__dirname + "/node_modules/font-awesome"));
app.use(serveStatic(__dirname + "/public"));
app.use("/doc", serveStatic(__dirname + "/doc"));
// Must be set here before the `ensureAuthentication` middleware because it
// must be accessed without Puavo credentials
app.use(require("./resources/emails"));
/**
* Set an instance of models.User to the request object when user has been
* authenticated
*
* @for server.Request
* @property {models.User} user
*/
app.use(function ensureAuthentication(req, res, next) {
if (!req.session.jwt) {
console.log("Not auth!");
return res.requestJwt();
}
User.byExternalId(req.session.jwt.id).fetch()
.then(function(user) {
if (!user) {
// The database was probably destroyed during development and the
// user in the session has disappeared. Just destroy the session
// and redirect to front page.
req.session.destroy();
return res.redirect("/");
}
req.sio = sio;
req.user = user;
next();
})
.catch(next);
});
app.use(function setDebugMode(req, res, next) {
req.debugMode = !PRODUCTION;
if (req.session.forceDebugMode) {
req.debugMode = true;
}
next();
});
app.get("/logout", function(req, res) {
req.session.destroy();
res.redirect("/");
});
app.get("/debugmode", function(req, res, next) {
req.session.forceDebugMode = !req.session.forceDebugMode;
console.log("forceDebugMode is now", req.session.forceDebugMode);
// req.session.save(function(err) {
// if (err) return next(err);
res.render("debugmode.ejs", { debugmode: req.session.forceDebugMode });
// });
});
app.use(require("./resources/tickets"));
app.use(require("./resources/comments"));
app.use(require("./resources/attachments"));
app.use(require("./resources/followers"));
app.use(require("./resources/visibilities"));
app.use(require("./resources/handlers"));
app.use(require("./resources/tags"));
app.use(require("./resources/notifications"));
app.use(require("./resources/titles"));
app.use("/api/puavo", require("./resources/puavo_api_proxy")(config));
app.get("/*", function(req, res) {
var jsBundle = "/build/bundle.min.js";
var cssBundle = "/build/styles.min.css";
var cacheKey = CACHE_KEY;
if (req.debugMode) {
console.log("Using debugmode for", req.user.getDomainUsername());
jsBundle = "/build/bundle.js";
cssBundle = "/build/styles.css";
cacheKey = Date.now();
}
res.render("index.ejs", {
jsBundle: jsBundle,
cssBundle: cssBundle,
cacheKey: cacheKey,
user: req.user.toJSON(),
serverHostname: HOSTNAME,
uptime: prettyMs(Date.now() - STARTED),
ptVersion: VERSION,
debugMode: req.debugMode,
});
});
app.use(function(err, req, res, next) {
if (process.env.NODE_ENV !== "test") next(err);
res.status(500).send(err.stack);
});
module.exports = app;
if (require.main === module) {
server.listen(config.port, function() {
console.log("Javascript API docs http://opinsys.github.io/puavo-ticket/");
console.log("REST API docs http://opinsys.github.io/puavo-ticket/rest");
var addr = server.address();
console.log('Listening on http://%s:%d', addr.address, addr.port);
});
if (!PRODUCTION) {
require("./devmode")(sio);
}
}
if (debugMem.name === "enabled") {
var filesize = require("filesize");
var last = process.memoryUsage().rss;
setInterval(function() {
var current = process.memoryUsage().rss;
var change = current - last;
last = current;
if (Math.abs(change) < 1024) return;
debugMem(
"Memory usage %s change from last %s",
filesize(current), filesize(change)
);
}, 500);
}
exec("dpkg -s puavo-ticket", function(err, stdout) {
if (err) {
return console.error("Failed to read puavo-ticket deb package version");
}
var re = /^Version: *(.+)/;
VERSION = stdout.split("\n").filter(function(line) {
return re.test(line);
}).map(function(line) {
return re.exec(line)[1];
})[0];
if (!PRODUCTION) return;
var shasum = crypto.createHash("sha1");
CACHE_KEY = shasum.update(VERSION).digest("hex");
});