diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..8568ba9 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,2 @@ +.git +node_modules \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f0103aa --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +npm-debug.log +node_modules +.idea +.dockerrun.sh +.vscode \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..b26bd55 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,17 @@ +FROM node:4.4.1-slim +MAINTAINER Rob Humphris + +# Copy the source to the Docker's usr directory +COPY . /usr/src/app + +# Set the working directory accordingly +WORKDIR /usr/src/app + +#Run npm install +RUN npm install + +#Expose node port +EXPOSE 3000 + +#Start mongo and node +CMD node appAcra.js \ No newline at end of file diff --git a/README.md b/README.md index 6f29856..525be15 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,15 @@ - ACRA Node Server (v. 0.0.3) + ACRA Node Server (v. 0.0.4) ================ Server [ACRA](http://acra.ch/) for [Node.js](http://nodejs.org/) with data base [Mongodb](http://www.mongodb.org/) Save all the crash reports in your own server. +### version 0.0.4 +* Updated to work with Express 4.x +* Updated to work with newer MongoDB + + ### version 0.0.3 * Statistics by android version. * Statistics by date error. @@ -15,14 +20,12 @@ Save all the crash reports in your own server. Technologies Used ------------ - Server = [node.js, express, ejs, mongodb, emailjs, node-properties-parser, colors, moment, async] Client = [bootstrap, jquery, tablesorter, jqplot] Installation ------------ - 1. Download and unzip (or git clone) into a directory. 2. Run "$ npm install" 3. Configure /acra_server.properties with mongodb, port web, user and password access and email credentials. @@ -31,7 +34,6 @@ Installation Philosophy ------------ - * Build a Server to replace Google Docs. * Write using modern tecnologies as Node.js and Mongodb. * Simple configuration with a only one properties file. @@ -39,7 +41,6 @@ Philosophy Features ------------ - * Basic front end web pages. * Send emails when receive ACRA report. * Login system to protect access. @@ -145,7 +146,6 @@ date_format=YYYY-MM-DD hh:mm:ss ``` ## Configuration Mongodb - Automatic configuration: * Creation of DB automatic @@ -153,7 +153,6 @@ Automatic configuration: * Independent collections by App ## Access server - * http://my_server:port_server (and login) diff --git a/acra_server.properties b/acra_server.properties index 0f8a8c0..d8c57d9 100644 --- a/acra_server.properties +++ b/acra_server.properties @@ -13,19 +13,20 @@ secret = b29a25fe160453b475d4243d12yrty342345752eeaa5bc # port mongodb mongodb_port = 27017 # Ip mongodb -mongodb_ip = localhost +#mongodb_ip = localhost +mongodb_ip = db.checkit-develop.elektron-dev.com # Name Data base name_database = acraloggerdb # CONFIGURATION MAIL # yes or no if want send email if acra error recive -send_mail = yes +send_mail = no # config connection email server user_mail= your_email@gmail.com password_mail = password_mail_gmail -host =smtp.gmail.com +host = smtp.gmail.com ssl = true # config email diff --git a/appAcra.js b/appAcra.js index f58597f..c3c8cbe 100644 --- a/appAcra.js +++ b/appAcra.js @@ -1,55 +1,85 @@ var express = require('express'); var colors = require('colors'); var prop = require('./properties.js'); -var logger = require('./logger'); +var logger = require('morgan'); +var acraLogger = require('./logger.js'); +var bodyParser = require('body-parser'); +var cookieParser = require('cookie-parser') +var favicon = require('serve-favicon'); +var session = require('express-session'); +var basicAuth = require('basic-auth'); +//function control errors +function clientErrorHandler(err, req, res, next) { + console.log('client error handler found in ip:'+req.ip, err); + res.sendStatus(500); + res.render('error', {locals: {"error":err} }); +} var app = express(); - -app.configure(function () { - app.use(express.logger('default')); /* 'default', 'short', 'tiny', 'dev' */ - app.use(express.bodyParser()); -}); - app.use(express.static(__dirname + '/public')); -app.use(express.favicon()); -app.use(express.cookieParser()); -app.use(express.cookieSession({ - key:prop.key, - secret :prop.secret, - cookie:{ path: '/', httpOnly: true, maxAge: null } -})); - +app.use(favicon(__dirname + '/public/favicon.ico')); +app.use(cookieParser()); app.use(clientErrorHandler); -app.use( express.bodyParser()); app.set('views', __dirname + '/views'); app.set('view engine', 'ejs'); -app.use(app.router); +app.use(logger('dev')); +app.use(bodyParser.json()); +function auth (req, res, next) { + function unauthorized(res) { + res.set('WWW-Authenticate', 'Basic realm=Authorization Required'); + return res.sendStatus(401); + }; + var user = basicAuth(req); -//function control errors -function clientErrorHandler(err, req, res, next) { - console.log('client error handler found in ip:'+req.ip, err); - res.status(500); - res.render('error', {locals: {"error":err} }); -} + if (!user || !user.name || !user.pass) { + return unauthorized(res); + }; + + if (user.name === prop.username && user.pass === prop.password) { + return next(); + } else { + return unauthorized(res); + }; +}; -var basicAuth = express.basicAuth(function(username, password) { - return (username == prop.username && password == prop.password); -}, 'Restrict area, please identify'); - //Mobile without auth -app.post('/logs/:appid', logger.addLog); +app.post('/logs/:appid', acraLogger.addLog); +app.put('/logs/:appid', acraLogger.addLog); + //Administration with auth -app.get('/logs/:appid/:id', basicAuth, logger.findByIdDetail); -app.get('/logsexport/:appid/:id', basicAuth, logger.findByIdDetailExport); -app.get('/logs/:appid', basicAuth, logger.findAll); -app.get('/logsexport/:appid', basicAuth, logger.findAllExport); -app.get('/mobiles', basicAuth, logger.findAllCollections); -app.get('/logs/:appid/:id/delete', basicAuth, logger.deleteLog); -app.get('/logout', logger.logout); +app.get('/logs/:appid/:id', auth, acraLogger.findByIdDetail); +app.get('/logsexport/:appid/:id', auth, acraLogger.findByIdDetailExport); +app.get('/logs/:appid', auth, acraLogger.findAll); +app.get('/logsexport/:appid', auth, acraLogger.findAllExport); +app.get('/mobiles', auth, acraLogger.findAllCollections); +app.get('/logs/:appid/:id/delete', auth, acraLogger.deleteLog); +app.get('/logout', acraLogger.logout); + +prop.loadProperties(() => { + acraLogger.open(prop, function(err) { + app.use(session({ + secret: prop.secret, + resave: false, + saveUninitialized: false, + cookie: { path: '/', httpOnly: true, secure: true, maxAge: null } + })); + console.log("------------------".yellow); + app.listen(prop.portWeb); + console.log('Listening on port '.yellow+prop.portWeb.red); + }); +}); + + + + + + + + -console.log("------------------".yellow); -app.listen(prop.portWeb); -console.log('Listening on port '.yellow+prop.portWeb.red); + + + diff --git a/dockerrun.sh b/dockerrun.sh new file mode 100644 index 0000000..fd3f094 --- /dev/null +++ b/dockerrun.sh @@ -0,0 +1 @@ +docker run -d --name acra-node-server --link mongo:mongodb elektron/acra-node-server \ No newline at end of file diff --git a/email.js b/email.js index e2bbbf1..93b5953 100644 --- a/email.js +++ b/email.js @@ -8,7 +8,7 @@ var server = email.server.connect({ ssl: prop.ssl }); -exports.send = function send (mobile,log) { +exports.send = function send(mobile,log) { if (prop.send_mail == 'yes') { console.log('Send email with error model:'+log.PHONE_MODEL); // send the message and get a callback with an error or details of the message that was sent @@ -24,7 +24,10 @@ exports.send = function send (mobile,log) { to: prop.to, cc: "", subject: prop.subject+ " from Mobile "+mobile - }, function(err, message) { console.log(err || message); }); + }, (err, message) => + { + console.log(err || message); + }); } } diff --git a/logger.js b/logger.js index 99aa00f..ff72ca8 100644 --- a/logger.js +++ b/logger.js @@ -1,44 +1,45 @@ var mongo = require('mongodb'); -var prop = require('./properties.js'); var email = require('./email.js'); var moment = require('moment'); var async = require('async'); var ejs = require('ejs'); -var DB_NAME = prop.name_database; - -var Server = mongo.Server, - Db = mongo.Db, - BSON = mongo.BSONPure; - -var server = new Server(prop.mongodbIp, prop.mongodbPort, {auto_reconnect: true, safe:false,journal:true}); -var db= new Db(DB_NAME, server); - -db.open(function(err, db) { - if(!err) { - console.log("Connected to data base ".yellow+DB_NAME.red); - console.log("------------------".yellow); - } -}); +var l = { + server: null, + db: null, + prop: null, + open: function(p, cb) { + l.prop = p; + l.server = new mongo.Server(l.prop.mongodbIp, l.prop.mongodbPort, {auto_reconnect: true, safe:false,journal:true}); + l.db = new mongo.Db(l.prop.name_database, l.server); + l.db.open(function(err, db) { + if(!err) { + console.log("Connected to data base ".yellow+l.prop.name_database.red); + console.log("------------------".yellow); + } + cb(err); + }); + } +}; //Export detail log of app in json format -exports.findByIdDetailExport = function(req, res) { +l.findByIdDetailExport = function(req, res) { var appid = req.params.appid; var id = req.params.id; console.log("findByIdDetailExport.appid:"+appid); console.log("findByIdDetailExport.id:"+id); - db.collection(appid, function(err, collection) { - collection.findOne({'_id':new BSON.ObjectID(id)}, function(err, item) { + l.db.collection(appid, function(err, collection) { + collection.findOne({'_id':new mongo.BSONPure.ObjectID(id)}, function(err, item) { res.send(item); }); }); }; //Export all logs of app in json format -exports.findAllExport = function(req, res) { +l.findAllExport = function(req, res) { var appid = req.params.appid; console.log("findAllExport.appid:"+appid); - db.collection(appid, function(err, collection) { + l.db.collection(appid, function(err, collection) { collection.find().toArray(function(err, items) { res.send(items); }); @@ -46,89 +47,71 @@ exports.findAllExport = function(req, res) { }; // VIEW - /views/detail.ejs -exports.findByIdDetail = function(req, res) { +l.findByIdDetail = function(req, res) { var appid = req.params.appid; var id = req.params.id; console.log("findByIdDetail.appid:"+appid); console.log("findByIdDetail.id:"+id); - db.collection(appid, function(err, collection) { - collection.findOne({'_id':new BSON.ObjectID(id)}, function(err, item) { - res.render('detail', {locals: {"log":item,"appid":appid,"id":id} }); + l.db.collection(appid, function(err, collection) { + collection.findOne({'_id':new mongo.BSONPure.ObjectID(id)}, function(err, item) { + res.render('detail', {log: item, appid: appid, id: id}); }); }); }; // VIEW - /views/listLogs.ejs -exports.findAll = function(req, res) { +l.findAll = function(req, res) { var appid = req.params.appid; console.log("findAll.appid:"+appid); loadListLogs(appid,res); }; // VIEW - /views/listMobiles.ejs -exports.findAllCollections = function(req, res) { - console.log("findAllCollections"); - db.collectionNames(function(err, names){ - res.render('listApps', {locals: {"list":names,"dbname":prop.name_database}}); - }); +l.findAllCollections = function(req, res) { + console.log("findAllCollections"); + l.db.listCollections().toArray(function(err, names) { + res.render('listApps', { list: names, dbname: l.prop.name_database }); + }); }; // VIEW - /views/delete.ejs -exports.deleteLog = function(req, res) { +l.deleteLog = function(req, res) { var appid = req.params.appid; var id = req.params.id; console.log("deleteLog.appid:"+appid); console.log("deleteLog.id:"+id); - db.collection(appid, function(err, collection) { - collection.remove({'_id':new BSON.ObjectID(id)}, {safe:true}, function(err, result) { - res.render('delete', {locals: {"appid":appid,"err":err}}); + l.db.collection(appid, function(err, collection) { + collection.remove({'_id':new mongo.BSONPure.ObjectID(id)}, {safe:true}, function(err, result) { + res.render('delete', {appid: appid, err: err}); }); }); } // IMPORTANT - Method without security access // Method to add info from mobile -exports.addLog = function(req, res) { +l.addLog = function(req, res) { var appid = req.params.appid; var log = req.body; console.log("addLog.appid:"+appid); - db.collection(appid, function(err, collection) { + l.db.collection(appid, function(err, collection) { collection.insert(log, {safe:true}, function(err, result) { if (err) { console.log("Add log error:"+err); res.send({'error':'An error has occurred'}); } else { - console.log("addLog:OK save"); - //format date text to date format - //to aggregate dates - formatDate(result,collection); - //After insert send email - email.send(appid,log); + console.log("addLog:OK save"); + email.send(appid, log); res.send(result[0]); } }); }); } -function formatDate(toSave,collection) { - var doc = toSave[0]; - doc.USER_CRASH_DATE = new Date(doc.USER_CRASH_DATE); - collection.update({_id:doc._id }, { - $set: { 'USER_CRASH_DATE': doc.USER_CRASH_DATE }, - }, function(err) { - if (!err) { - console.log("Error:"+err); - } else { - console.log("Modify date format"); - } - }); -} - //Logout and delete cookie -exports.logout = function (req, res) { +l.logout = function (req, res) { console.log("logout"); req.session = null; - res.clearCookie(prop.key); + res.clearCookie(l.prop.key); res.redirect('/index.html'); } @@ -138,7 +121,7 @@ function loadListLogs(appid,res) { var resultSearch = {}; async.parallel([ function(callback) { - db.collection(appid, function(err, collection) { + l.db.collection(appid, function(err, collection) { collection.aggregate([ { $group : { _id : {android :"$ANDROID_VERSION"} , number : { $sum : 1 } } }, { $sort : { number : -1 } }, @@ -149,7 +132,7 @@ function loadListLogs(appid,res) { }); }); }, function(callback) { - db.collection(appid, function(err, collection) { + l.db.collection(appid, function(err, collection) { collection.aggregate([ { $group : { _id : {movile :"$PHONE_MODEL"} , number : { $sum : 1 } } }, { $sort : { number : -1 } }, @@ -161,7 +144,7 @@ function loadListLogs(appid,res) { }); }, function(callback) { - db.collection(appid, function(err, collection) { + l.db.collection(appid, function(err, collection) { collection.aggregate([ { $group : { _id : {year:{$year :"$USER_CRASH_DATE"},month:{$month :"$USER_CRASH_DATE"}} , number : { $sum : 1 } } }, { $sort : { _id : -1 } }, @@ -173,11 +156,11 @@ function loadListLogs(appid,res) { }); }, function(callback) { - db.collection(appid, function(err, collection) { + l.db.collection(appid, function(err, collection) { collection.find().toArray(function(err, items) { for (var i = 0; i < items.length; i++) { if (items[i].USER_APP_START_DATE.length > 0 ) { - items[i].USER_APP_START_DATE = moment(items[i].USER_APP_START_DATE).format(prop.date_format); + items[i].USER_APP_START_DATE = moment(items[i].USER_APP_START_DATE).format(l.prop.date_format); } } resultSearch.logs = items; @@ -186,7 +169,8 @@ function loadListLogs(appid,res) { }); } ], function(err) { - res.render('listLogs', {locals: {"list":resultSearch.logs,"mobiles":resultSearch.agg_phone,"android":resultSearch.android,"dates":resultSearch.dates,"appid":appid} }); + res.render('listLogs', {list: resultSearch.logs, mobiles: resultSearch.agg_phone, android: resultSearch.android, dates: resultSearch.dates, appid: appid}); }); } +module.exports = l; \ No newline at end of file diff --git a/package.json b/package.json index 6cf872e..808bae5 100644 --- a/package.json +++ b/package.json @@ -1,18 +1,33 @@ { - "name": "acra_server_log", + "name": "acra-node-server", "description": "Acra Server with nodejs and mongodb", "author": "Diego Martin Moreno", - "version": "0.0.3", + "version": "0.0.4", + "main": "appAcra.js", + "repository": { + "type": "git", + "url": "https://github.com/RobHumphris/acra-node-server.git" + }, "dependencies": { - "express": ">= 3.1.0", - "mongodb":">= 1.2.14", + "async": ">= 1.5.2", + "basic-auth": ">= 1.0.3", + "body-parser": ">= 1.15.0", + "colors": ">= 0.6.0-1", + "cookie-parser": ">= 1.4.1", + "ejs": ">= 2.4.1", + "emailjs": ">= 1.0.4", + "express": ">= 4.13.4", + "express-session": ">= 1.13.0", "log4js": ">= 0.5.7", - "emailjs" : ">= 0.3.4", - "colors" : ">= 0.6.0-1", - "ejs" : ">= 0.8.3", - "moment" : ">= 2.0.0", - "async" : ">= 0.2.8", - "node-properties-parser" : ">= 0.0.2" + "moment": ">= 2.0.0", + "mongodb": ">= 1.2.14", + "morgan": ">= 1.7.0", + "properties-parser": ">= 0.3.1", + "serve-favicon": ">= 2.3.0" }, - "engine": "node >= 0.8.0" -} \ No newline at end of file + "devDependencies": { + "mocha": "^2.2.5", + "supertest": "^1.0.1", + "chai": "^2.3.0" + } +} diff --git a/properties.js b/properties.js index 43e15ee..c2837c8 100644 --- a/properties.js +++ b/properties.js @@ -1,4 +1,4 @@ -var proper = require("node-properties-parser"); +var proper = require("properties-parser"); var colors = require('colors'); var PORT_WEB = "port_web"; @@ -19,66 +19,52 @@ var FROM = "from"; var TO = "to"; var DATE_FORMAT = "date_format"; -console.log("------------------------".red); -console.log("ACRA SERVER LOG 0.0.3".bold.red); -console.log("------------------------".red); -console.log("Loading properties sync before start server...".red); -var par = proper.readSync("./acra_server.properties"); - -console.log("------------------------".red); - -exports.portWeb = portWeb = par[PORT_WEB]; -console.log("port_web:"+portWeb.red); - -exports.mongodbPort = mongodbPort = par[MONGODB_PORT]; -console.log("mongodbPort:"+mongodbPort.red); - -exports.mongodbIp = mongodbIp = par[MONGODB_IP]; -console.log("mongodbIp:"+mongodbIp.red); - -exports.username = username = par[USERNAME]; -console.log("username:"+username.red); - -exports.password = password = par[PASSWORD]; -console.log("password:"+password.red); - -exports.name_database = name_database = par[NAME_DATABASE]; -console.log("name_database:"+name_database.red); - -exports.secret = secret = par[SECRET]; -console.log("secret:"+secret.red); - -exports.key = key = par[KEY]; -console.log("key:"+key.red); - -exports.send_mail = send_mail = par[SEND_MAIL]; -console.log("send_mail:"+send_mail.red); - -exports.user_mail = user_mail = par[USER_MAIL]; -console.log("user_mail:"+user_mail.red); - -exports.password_mail = password_mail = par[PASSWORD_MAIL]; -console.log("password_mail:"+password_mail.red); - -exports.host = host = par[HOST]; -console.log("host:"+host.red); - -exports.ssl = ssl = par[SSL]; -console.log("ssl:"+ssl.red); - -exports.subject = subject = par[SUBJECT]; -console.log("subject:"+subject.red); - -exports.from = from = par[FROM]; -console.log("from:"+from.red); - -exports.to = to = par[TO]; -console.log("to:"+to.red); - -exports.date_format = date_format = par[DATE_FORMAT]; -console.log("date_format:"+date_format.red); - -console.log("------------------------".red); - - - +var p = {}; + +p.loadProperties = function(next) { + proper.read('./acra_server.properties', function(err, data) { + console.log("------------------------".red); + console.log("ACRA SERVER LOG 0.0.4".bold.red); + console.log("------------------------".red); + console.log("Loading properties sync before start server...".red); + console.log("------------------------".red); + p.portWeb = portWeb = data[PORT_WEB]; + console.log("port_web:"+p.portWeb.red); + p.mongodbPort = mongodbPort = process.env.ACRA_MONGODB_PORT || data[MONGODB_PORT]; + console.log("mongodbPort:"+p.mongodbPort.red); + p.mongodbIp = mongodbIp = process.env.ACRA_MONGODB_IP || data[MONGODB_IP]; + console.log("mongodbIp:"+p.mongodbIp.red); + p.username = username = process.env.ACRA_USERNAME || data[USERNAME]; + console.log("username:"+p.username.red); + p.password = password = process.env.ACRA_PASSWORD || data[PASSWORD]; + console.log("password:"+p.password.red); + p.name_database = name_database = process.env.ACRA_DB || data[NAME_DATABASE]; + console.log("name_database:"+p.name_database.red); + p.secret = secret = process.env.ACRA_SECRET ||data[SECRET]; + console.log("secret:"+p.secret.red); + p.key = key = process.env.ACRA_KEY || data[KEY]; + console.log("key:"+p.key.red); + p.send_mail = send_mail = process.env.ACRA_SEND_MAIL || data[SEND_MAIL]; + console.log("send_mail:"+p.send_mail.red); + p.user_mail = user_mail = process.env.ACRA_USER_MAIL || data[USER_MAIL]; + console.log("user_mail:"+p.user_mail.red); + p.password_mail = password_mail = process.env.ACRA_PASSWORD_MAIL ||data[PASSWORD_MAIL]; + console.log("password_mail:"+p.password_mail.red); + p.host = host = process.env.ACRA_HOST || data[HOST]; + console.log("host:"+p.host.red); + p.ssl = ssl = data[SSL]; + console.log("ssl:"+p.ssl.red); + p.subject = subject = data[SUBJECT]; + console.log("subject:"+p.subject.red); + p.from = from = data[FROM]; + console.log("from:"+from.red); + p.to = to = data[TO]; + console.log("to:"+p.to.red); + p.date_format = date_format = data[DATE_FORMAT]; + console.log("date_format:"+p.date_format.red); + console.log("------------------------".red); + next(); + }); +}; + +module.exports = p; diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..b4daca2 Binary files /dev/null and b/public/favicon.ico differ diff --git a/public/index.html b/public/index.html index 404e77d..5f03a74 100644 --- a/public/index.html +++ b/public/index.html @@ -63,7 +63,7 @@

ACRA Node Server

- © 2013 Sinclinal.com - Version 0.0.3 + © 2013 Sinclinal.com - Version 0.0.4