port most backend functionality, further cleanups, basic electron support

This commit is contained in:
Lukas F. Hartmann 2018-04-12 16:38:48 +02:00
parent 8dc48a84ba
commit 08b81d5ff4
19 changed files with 331 additions and 372 deletions

189
app.js
View File

@ -1,172 +1,33 @@
"use strict"; const spacedeck = require('./spacedeck')
const db = require('./models/db.js'); const electron = require('electron')
require("log-timestamp"); const electronApp = electron.app
const BrowserWindow = electron.BrowserWindow
let mainWindow
const config = require('config'); function createWindow () {
const redis = require('./helpers/redis'); mainWindow = new BrowserWindow({width: 1200, height: 700})
const websockets = require('./helpers/websockets'); mainWindow.loadURL("http://localhost:9666")
mainWindow.on('closed', function () {
const http = require('http'); mainWindow = null
const path = require('path'); })
const _ = require('underscore');
const favicon = require('serve-favicon');
const logger = require('morgan');
const cookieParser = require('cookie-parser');
const bodyParser = require('body-parser');
//const mongoose = require('mongoose');
const swig = require('swig');
const i18n = require('i18n-2');
const helmet = require('helmet');
const express = require('express');
const app = express();
const serveStatic = require('serve-static');
const isProduction = app.get('env') === 'production';
console.log("Booting Spacedeck Open… (environment: " + app.get('env') + ")");
app.use(logger(isProduction ? 'combined' : 'dev'));
i18n.expressBind(app, {
locales: ["en", "de", "fr"],
defaultLocale: "en",
cookieName: "spacedeck_locale",
devMode: (app.get('env') == 'development')
});
swig.setDefaults({
varControls: ["[[", "]]"] // otherwise it's not compatible with vue.js
});
swig.setFilter('cdn', function(input, idx) {
return input;
});
app.engine('html', swig.renderFile);
app.set('view engine', 'html');
if (isProduction) {
app.set('views', path.join(__dirname, 'build', 'views'));
app.use(favicon(path.join(__dirname, 'build', 'assets', 'images', 'favicon.png')));
app.use(express.static(path.join(__dirname, 'build', 'assets')));
} else {
app.set('views', path.join(__dirname, 'views'));
app.use(favicon(path.join(__dirname, 'public', 'images', 'favicon.png')));
app.use(express.static(path.join(__dirname, 'public')));
} }
app.use(bodyParser.json({ electronApp.on('ready', createWindow)
limit: '50mb'
}));
app.use(bodyParser.urlencoded({ // Quit when all windows are closed.
extended: false, electronApp.on('window-all-closed', function () {
limit: '50mb' // On OS X it is common for applications and their menu bar
})); // to stay active until the user quits explicitly with Cmd + Q
if (process.platform !== 'darwin') {
app.use(cookieParser()); electronApp.quit()
app.use(helmet.frameguard())
app.use(helmet.xssFilter())
app.use(helmet.hsts({
maxAge: 7776000000,
includeSubdomains: true
}))
app.disable('x-powered-by');
app.use(helmet.noSniff())
//app.use(require("./middlewares/error_helpers"));
app.use(require("./middlewares/session"));
//app.use(require("./middlewares/cors"));
//app.use(require("./middlewares/i18n"));
app.use("/api", require("./middlewares/api_helpers"));
app.use('/api/spaces/:id', require("./middlewares/space_helpers"));
app.use('/api/spaces/:id/artifacts/:artifact_id', require("./middlewares/artifact_helpers"));
app.use('/api/users', require('./routes/api/users'));
app.use('/api/memberships', require('./routes/api/memberships'));
const spaceRouter = require('./routes/api/spaces');
app.use('/api/spaces', spaceRouter);
spaceRouter.use('/:id/artifacts', require('./routes/api/space_artifacts'));
spaceRouter.use('/:id/memberships', require('./routes/api/space_memberships'));
spaceRouter.use('/:id/messages', require('./routes/api/space_messages'));
spaceRouter.use('/:id/digest', require('./routes/api/space_digest'));
spaceRouter.use('/:id', require('./routes/api/space_exports'));
app.use('/api/sessions', require('./routes/api/sessions'));
//app.use('/api/webgrabber', require('./routes/api/webgrabber'));
app.use('/', require('./routes/root'));
if (config.get('storage_local_path')) {
app.use('/storage', serveStatic(config.get('storage_local_path')+"/"+config.get('storage_bucket'), {
maxAge: 24*3600
}));
}
// catch 404 and forward to error handler
//app.use(require('./middlewares/404'));
if (app.get('env') == 'development') {
app.set('view cache', false);
swig.setDefaults({cache: false});
} else {
app.use(require('./middlewares/500'));
}
module.exports = app;
// CONNECT TO DATABASE
db.init();
// START WEBSERVER
const port = 9666;
const server = http.Server(app).listen(port, () => {
if ("send" in process) {
process.send('online');
} }
})
}).on('listening', () => { electronApp.on('activate', function () {
// On OS X it's common to re-create a window in the app when the
const host = server.address().address; // dock icon is clicked and there are no other windows open.
const port = server.address().port; if (mainWindow === null) {
console.log('Spacedeck Open listening at http://%s:%s', host, port); createWindow()
}).on('error', (error) => {
if (error.syscall !== 'listen') {
throw error;
} }
})
const bind = typeof port === 'string' ? 'Pipe ' + port : 'Port ' + port;
switch (error.code) {
case 'EACCES':
console.error(bind + ' requires elevated privileges');
process.exit(1);
break;
case 'EADDRINUSE':
console.error(bind + ' is already in use');
process.exit(1);
break;
default:
throw error;
}
});
//WEBSOCKETS & WORKER
websockets.startWebsockets(server);
redis.connectRedis();
process.on('message', (message) => {
console.log("Process message:", message);
if (message === 'shutdown') {
console.log("Exiting Spacedeck.");
process.exit(0);
}
});

View File

@ -1,7 +1,7 @@
'use strict'; 'use strict';
var swig = require('swig'); var swig = require('swig');
var AWS = require('aws-sdk'); //var AWS = require('aws-sdk');
module.exports = { module.exports = {
sendMail: (to_email, subject, body, options) => { sendMail: (to_email, subject, body, options) => {
@ -29,9 +29,9 @@ module.exports = {
options: options options: options
}); });
if (process.env.NODE_ENV === 'development') { //if (process.env.NODE_ENV === 'development') {
console.log("Email: to " + to_email + " in production.\nreply_to: " + reply_to + "\nsubject: " + subject + "\nbody: \n" + htmlText + "\n\n plaintext:\n" + plaintext); console.log("Email: to " + to_email + " in production.\nreply_to: " + reply_to + "\nsubject: " + subject + "\nbody: \n" + htmlText + "\n\n plaintext:\n" + plaintext);
} else { /*} else {
AWS.config.update({region: 'eu-west-1'}); AWS.config.update({region: 'eu-west-1'});
var ses = new AWS.SES(); var ses = new AWS.SES();
@ -56,6 +56,6 @@ module.exports = {
if (err) console.error("Error sending email:", err); if (err) console.error("Error sending email:", err);
else console.log("Email sent."); else console.log("Email sent.");
}); });
} }*/
} }
}; };

View File

@ -8,7 +8,7 @@ const config = require('config');
const WebSocketServer = require('ws').Server; const WebSocketServer = require('ws').Server;
const RedisConnection = require('ioredis'); //const RedisConnection = require('ioredis');
const async = require('async'); const async = require('async');
const _ = require("underscore"); const _ = require("underscore");
const crypto = require('crypto'); const crypto = require('crypto');

View File

@ -10,8 +10,8 @@ module.exports = (req, res, next) => {
req.i18n.setLocaleFromCookie(); req.i18n.setLocaleFromCookie();
} }
if (req.user && req.user.preferences.language) { if (req.user && req.user.prefs_language) {
req.i18n.setLocale(req.user.preferences.language); req.i18n.setLocale(req.user.prefs_language);
} }
next(); next();
} }

View File

@ -198,6 +198,48 @@ module.exports = {
}), }),
init: function() { init: function() {
User = this.User;
Session = this.Session;
Space = this.Space;
Artifact = this.Artifact;
Message = this.Message;
Membership = this.Membership;
Space.belongsTo(User, {
foreignKey: {
name: 'creator_id'
},
as: 'creator'
});
Artifact.belongsTo(User, {
foreignKey: {
name: 'user_id'
},
as: 'user'
});
Artifact.belongsTo(Space, {
foreignKey: {
name: 'space_id'
},
as: 'space'
});
Message.belongsTo(User, {
foreignKey: {
name: 'user_id'
},
as: 'user'
});
Message.belongsTo(Space, {
foreignKey: {
name: 'space_id'
},
as: 'space'
});
sequelize.sync(); sequelize.sync();
}, },
@ -222,7 +264,6 @@ module.exports = {
}); });
} else { } else {
// reached the top // reached the top
var role = prevRole; var role = prevRole;
space.memberships = currentMemberships; space.memberships = currentMemberships;
@ -252,10 +293,10 @@ module.exports = {
}, },
findUserBySessionToken: (token, cb) => { findUserBySessionToken: (token, cb) => {
db.Session.findOne({where: {token: token}}) Session.findOne({where: {token: token}})
.then(session => { .then(session => {
if (!session) cb(null, null) if (!session) cb(null, null)
else db.User.findOne({where: {_id: session.user_id}}) else User.findOne({where: {_id: session.user_id}})
.then(user => { .then(user => {
cb(null, user) cb(null, user)
}) })

View File

@ -3,8 +3,7 @@
"version": "1.0.0", "version": "1.0.0",
"private": true, "private": true,
"scripts": { "scripts": {
"start": "nodemon -e .js,.html bin/www", "start": "electron ."
"test": "mocha"
}, },
"engines": { "engines": {
"node": ">=7.8.0" "node": ">=7.8.0"
@ -12,48 +11,26 @@
"dependencies": { "dependencies": {
"archiver": "1.3.0", "archiver": "1.3.0",
"async": "2.3.0", "async": "2.3.0",
"aws-sdk": "2.39.0",
"basic-auth": "1.1.0", "basic-auth": "1.1.0",
"bcryptjs": "2.4.3", "bcryptjs": "2.4.3",
"body-parser": "~1.17.1", "body-parser": "~1.17.1",
"cheerio": "0.22.0", "cheerio": "0.22.0",
"config": "1.25.1", "config": "1.25.1",
"cookie-parser": "~1.4.3", "cookie-parser": "~1.4.3",
"csurf": "1.9.0",
"debug": "~2.6.3",
"electron": "^1.8.4", "electron": "^1.8.4",
"execSync": "latest", "execSync": "latest",
"express": "~4.13.0", "express": "~4.13.0",
"extract-zip": "^1.6.6",
"file-type": "^7.6.0", "file-type": "^7.6.0",
"glob": "7.1.1", "glob": "7.1.1",
"gm": "1.23.0", "gm": "1.23.0",
"googleapis": "18.0.0",
"gulp": "^3.9.1",
"gulp-concat": "2.6.0",
"gulp-express": "0.3.0",
"gulp-nodemon": "*",
"gulp-sass": "^2.0.3",
"gulp-uglify": "^1.5.1",
"gulp-util": "^3.0.6",
"helmet": "^3.5.0", "helmet": "^3.5.0",
"i18n-2": "0.6.3", "i18n-2": "0.6.3",
"ioredis": "2.5.0",
"lodash": "^4.3.0",
"log-timestamp": "latest", "log-timestamp": "latest",
"md5": "2.2.1", "morgan": "1.8.1",
"mime-types": "^2.1.18",
"mock-aws-s3": "^2.6.0", "mock-aws-s3": "^2.6.0",
"moment": "^2.19.3", "moment": "^2.19.3",
"mongoose": "4.9.3",
"morgan": "1.8.1",
"node-phantom-simple": "2.2.4", "node-phantom-simple": "2.2.4",
"node-sass-middleware": "0.11.0",
"pdfkit": "0.8.0",
"phantomjs-prebuilt": "2.1.14", "phantomjs-prebuilt": "2.1.14",
"pm2": "latest",
"qr-image": "3.2.0",
"raven": "1.2.0",
"read-chunk": "^2.1.0", "read-chunk": "^2.1.0",
"request": "2.81.0", "request": "2.81.0",
"sanitize-html": "^1.11.1", "sanitize-html": "^1.11.1",
@ -66,29 +43,10 @@
"underscore": "1.8.3", "underscore": "1.8.3",
"uuid": "^3.2.1", "uuid": "^3.2.1",
"validator": "7.0.0", "validator": "7.0.0",
"weak": "1.0.1",
"ws": "2.2.3" "ws": "2.2.3"
}, },
"devDependencies": { "main": "app.js",
"express": "^4.13.3",
"gulp": "^3.9.1",
"gulp-clean": "^0.3.2",
"gulp-concat": "^2.6.0",
"gulp-express": "^0.3.0",
"gulp-fingerprint": "^0.3.2",
"gulp-nodemon": "^2.0.4",
"gulp-rev": "^7.1.2",
"gulp-rev-all": "^0.9.7",
"gulp-rev-replace": "^0.4.3",
"gulp-sass": "^3.1.0",
"gulp-uglify": "^2.1.2",
"nodemon": "1.11.0",
"should": "^11.2.1",
"supertest": "^3.0.0",
"winston": "^2.3.1"
},
"description": "", "description": "",
"main": "Gulpfile.js",
"directories": {}, "directories": {},
"repository": { "repository": {
"type": "git", "type": "git",

View File

@ -13,19 +13,17 @@ SpacedeckAccount = {
methods: { methods: {
show_account: function(user) { show_account: function(user) {
this.activate_dropdown('account'); this.activate_dropdown('account');
this.load_subscription();
this.load_billing();
}, },
account_save_user_digest: function(val) { account_save_user_digest: function(val) {
this.user.preferences.daily_digest = val; this.user.prefs_email_digest = val;
this.save_user(function(){ this.save_user(function() {
}); });
}, },
account_save_user_notifications: function(val) { account_save_user_notifications: function(val) {
this.user.preferences.email_notifications = val; this.user.prefs_email_notifications = val;
this.save_user(function(){ this.save_user(function() {
}); });
}, },

View File

@ -170,7 +170,6 @@ var SpacedeckRoutes = {
location.href = "/"; location.href = "/";
} else { } else {
this.active_view = "account"; this.active_view = "account";
this.load_subscription();
} }
}.bind(this) }.bind(this)
} }

View File

@ -158,7 +158,7 @@ function boot_spacedeck() {
}); });
} }
$(document).ready(function(){ document.addEventListener("DOMContentLoaded",function() {
window.smoke = smoke; window.smoke = smoke;
window.alert = smoke.alert; window.alert = smoke.alert;

View File

@ -6,12 +6,10 @@ require('../../models/db');
var async = require('async'); var async = require('async');
var fs = require('fs'); var fs = require('fs');
var _ = require("underscore"); var _ = require("underscore");
var mongoose = require("mongoose");
var request = require('request'); var request = require('request');
var url = require("url"); var url = require("url");
var path = require("path"); var path = require("path");
var crypto = require('crypto'); var crypto = require('crypto');
var qr = require('qr-image');
var glob = require('glob'); var glob = require('glob');
var gm = require('gm'); var gm = require('gm');

View File

@ -2,7 +2,6 @@
var config = require('config'); var config = require('config');
const db = require('../../models/db'); const db = require('../../models/db');
var redis = require('../../helpers/redis');
var mailer = require('../../helpers/mailer'); var mailer = require('../../helpers/mailer');
var uploader = require('../../helpers/uploader'); var uploader = require('../../helpers/uploader');
var space_render = require('../../helpers/space-render'); var space_render = require('../../helpers/space-render');
@ -12,13 +11,11 @@ var async = require('async');
var moment = require('moment'); var moment = require('moment');
var fs = require('fs'); var fs = require('fs');
var _ = require("underscore"); var _ = require("underscore");
var mongoose = require("mongoose");
var archiver = require('archiver'); var archiver = require('archiver');
var request = require('request'); var request = require('request');
var url = require("url"); var url = require("url");
var path = require("path"); var path = require("path");
var crypto = require('crypto'); var crypto = require('crypto');
var qr = require('qr-image');
var glob = require('glob'); var glob = require('glob');
var gm = require('gm'); var gm = require('gm');
var sanitizeHtml = require('sanitize-html'); var sanitizeHtml = require('sanitize-html');

View File

@ -14,7 +14,6 @@ var phantom = require('../../helpers/phantom');
var async = require('async'); var async = require('async');
var fs = require('fs'); var fs = require('fs');
var _ = require("underscore"); var _ = require("underscore");
var mongoose = require("mongoose");
var archiver = require('archiver'); var archiver = require('archiver');
var request = require('request'); var request = require('request');
var url = require("url"); var url = require("url");
@ -52,8 +51,7 @@ var roleMapping = {
router.get('/', function(req, res, next) { router.get('/', function(req, res, next) {
db.Message.findAll({where:{ db.Message.findAll({where:{
space_id: req.space._id space_id: req.space._id
}}) }, include: ['user']})
//.populate('user', userMapping)
.then(function(messages) { .then(function(messages) {
res.status(200).json(messages); res.status(200).json(messages);
}); });
@ -65,6 +63,7 @@ router.post('/', function(req, res, next) {
if (req.user) { if (req.user) {
attrs.user = req.user; attrs.user = req.user;
attrs.user_id = req.user._id;
} else { } else {
attrs.user = null; attrs.user = null;
} }
@ -72,7 +71,7 @@ router.post('/', function(req, res, next) {
var msg = attrs; var msg = attrs;
msg._id = uuidv4(); msg._id = uuidv4();
db.Message.create(msg, function() { db.Message.create(msg).then(function() {
if (msg.message.length <= 1) return; if (msg.message.length <= 1) return;
// TODO reimplement notifications // TODO reimplement notifications
res.distributeCreate("Message", msg); res.distributeCreate("Message", msg);
@ -82,19 +81,12 @@ router.post('/', function(req, res, next) {
router.delete('/:message_id', function(req, res, next) { router.delete('/:message_id', function(req, res, next) {
db.Message.findOne({where:{ db.Message.findOne({where:{
"_id": req.params.message_id "_id": req.params.message_id
}}, function(msg) { }}).then(function(msg) {
if (!msg) { if (!msg) {
res.sendStatus(404); res.sendStatus(404);
} else { } else {
msg.destroy(function(err) { msg.destroy().then(function() {
if (err) res.status(400).json(err); res.distributeDelete("Message", msg);
else {
if (msg) {
res.distributeDelete("Message", msg);
} else {
res.sendStatus(404);
}
}
}); });
} }
}); });

View File

@ -17,7 +17,6 @@ var slug = require('slug');
var fs = require('fs'); var fs = require('fs');
var async = require('async'); var async = require('async');
var _ = require("underscore"); var _ = require("underscore");
var mongoose = require("mongoose");
var request = require('request'); var request = require('request');
var url = require("url"); var url = require("url");
var path = require("path"); var path = require("path");
@ -67,6 +66,7 @@ router.get('/', function(req, res, next) {
return m.space_id; return m.space_id;
}); });
// TODO port
var q = { var q = {
"space_type": "folder", "space_type": "folder",
"$or": [{ "$or": [{
@ -81,13 +81,11 @@ router.get('/', function(req, res, next) {
}] }]
}; };
Space db.Space
.find(q) .findAll({where: q})
.populate('creator', userMapping) .then(function(spaces) {
.exec(function(err, spaces) {
if (err) console.error(err);
var updatedSpaces = spaces.map(function(s) { var updatedSpaces = spaces.map(function(s) {
var spaceObj = s.toObject(); var spaceObj = s; //.toObject();
return spaceObj; return spaceObj;
}); });
@ -104,18 +102,17 @@ router.get('/', function(req, res, next) {
return s.space_type == "folder"; return s.space_type == "folder";
}) })
var uniqueFolders = _.unique(onlyFolders, (s) => { var uniqueFolders = _.unique(onlyFolders, (s) => {
return s._id.toString(); return s._id;
}) })
res.status(200).json(uniqueFolders); res.status(200).json(uniqueFolders);
}); });
}); });
}); });
} else if (req.query.search) { } else if (req.query.search) {
db.Membership.findAll({where:{ db.Membership.findAll({where:{
user: req.user._id user_id: req.user._id
}}).then(memberships => { }}).then(memberships => {
var validMemberships = memberships.filter(function(m) { var validMemberships = memberships.filter(function(m) {
@ -131,21 +128,15 @@ router.get('/', function(req, res, next) {
// TODO FIXME port // TODO FIXME port
var q = { where: { var q = { where: {
"$or": [{"creator_id": req.user._id}, [Op.or]: [{"creator_id": req.user._id},
{"_id": {"$in": spaceIds}}, {"_id": {[Op.in]: spaceIds}},
{"parent_space_id": {"$in": spaceIds}}], {"parent_space_id": {[Op.in]: spaceIds}}],
name: new RegExp(req.query.search, "i")} name: {[Op.like]: "%"+req.query.search+"%"}
}; }, include: ['creator']};
db.Space db.Space
.findAll(q) .findAll(q)
//.populate('creator', userMapping)
.then(function(spaces) { .then(function(spaces) {
if (err) console.error(err);
var updatedSpaces = spaces.map(function(s) {
var spaceObj = s.toObject();
return spaceObj;
});
res.status(200).json(spaces); res.status(200).json(spaces);
}); });
}); });
@ -161,7 +152,7 @@ router.get('/', function(req, res, next) {
if (space) { if (space) {
db.getUserRoleInSpace(space, req.user, function(role) { db.getUserRoleInSpace(space, req.user, function(role) {
if (role == "none") { if (role == "none") {
if(space.access_mode == "public") { if (space.access_mode == "public") {
role = "viewer"; role = "viewer";
} }
} }
@ -170,8 +161,7 @@ router.get('/', function(req, res, next) {
db.Space db.Space
.findAll({where:{ .findAll({where:{
parent_space_id: req.query.parent_space_id parent_space_id: req.query.parent_space_id
}}) }, include:['creator']})
//.populate('creator', userMapping)
.then(function(spaces) { .then(function(spaces) {
res.status(200).json(spaces); res.status(200).json(spaces);
}); });
@ -214,7 +204,7 @@ router.get('/', function(req, res, next) {
}; };
db.Space db.Space
.findAll({where: q}) .findAll({where: q, include: ['creator']})
.then(function(spaces) { .then(function(spaces) {
var updatedSpaces = spaces.map(function(s) { var updatedSpaces = spaces.map(function(s) {
var spaceObj = db.spaceToObject(s); var spaceObj = db.spaceToObject(s);
@ -414,15 +404,12 @@ router.delete('/:id', function(req, res, next) {
if (req.spaceRole == "admin") { if (req.spaceRole == "admin") {
const attrs = req.body; const attrs = req.body;
Space.recursiveDelete(space, function(err) { space.destroy().then(function() {
if (err) res.status(400).json(err); res.distributeDelete("Space", space);
else {
res.distributeDelete("Space", space);
}
}); });
} else { } else {
res.status(403).json({ res.status(403).json({
"error": "requires admin status" "error": "requires admin role"
}); });
} }
} else { } else {

View File

@ -74,14 +74,12 @@ router.post('/', function(req, res) {
res.sendStatus(400); res.sendStatus(400);
}) })
.then(u => { .then(u => {
console.log("!!! created user:", u);
var homeSpace = { var homeSpace = {
_id: uuidv4(), _id: uuidv4(),
name: req.i18n.__("home"), name: req.i18n.__("home"),
space_type: "folder", space_type: "folder",
creator_id: u._id creator_id: u._id
}; };
db.Space.create(homeSpace) db.Space.create(homeSpace)
.error(err => { .error(err => {
res.sendStatus(400); res.sendStatus(400);
@ -109,8 +107,6 @@ router.post('/', function(req, res) {
}); });
}; };
console.log("!!! hello !!!");
db.User.findAll({where: {email: email}}) db.User.findAll({where: {email: email}})
.then(users => { .then(users => {
if (users.length == 0) { if (users.length == 0) {
@ -131,19 +127,15 @@ router.get('/current', function(req, res, next) {
}); });
router.put('/:id', function(req, res, next) { router.put('/:id', function(req, res, next) {
// TODO explicit whitelisting
var user = req.user; var user = req.user;
console.log(req.params.id, user._id);
if (user._id == req.params.id) { if (user._id == req.params.id) {
var newAttr = req.body; var newAttr = req.body;
newAttr.updated_at = new Date(); newAttr.updated_at = new Date();
delete newAttr['_id']; delete newAttr['_id'];
User.findOneAndUpdate({"_id": user._id}, {"$set": newAttr}, function(err, updatedUser) { db.User.update(newAttr, {where: {"_id": user._id}}).then(function(updatedUser) {
if (err) { res.status(200).json(newAttr);
res.sendStatus(400);
} else {
res.status(200).json(updatedUser);
}
}); });
} else { } else {
res.sendStatus(403); res.sendStatus(403);
@ -161,12 +153,8 @@ router.post('/:id/password', function(req, res, next) {
bcrypt.genSalt(10, function(err, salt) { bcrypt.genSalt(10, function(err, salt) {
bcrypt.hash(pass, salt, function(err, hash) { bcrypt.hash(pass, salt, function(err, hash) {
user.password_hash = hash; user.password_hash = hash;
user.save(function(err){ user.save().then(function() {
if(err){ res.sendStatus(204);
res.status(400).json(err);
}else{
res.sendStatus(204);
}
}); });
}); });
}); });
@ -195,7 +183,7 @@ router.delete('/:id', (req, res, next) => {
} }
} else { } else {
user.remove((err) => { user.remove((err) => {
if(err)res.status(400).json(err); if (err) res.status(400).json(err);
else res.sendStatus(204); else res.sendStatus(204);
}); });
} }
@ -239,19 +227,15 @@ router.post('/:user_id/avatar', (req, res, next) => {
if (err) res.status(400).json(err); if (err) res.status(400).json(err);
else { else {
user.avatar_thumb_uri = url; user.avatar_thumb_uri = url;
user.save((err, updatedUser) => { user.save().then(() => {
if (err) { fs.unlink(localResizedFilePath, (err) => {
res.sendStatus(400); if (err) {
} else { console.error(err);
fs.unlink(localResizedFilePath, (err) => { res.status(400).json(err);
if (err) { } else {
console.error(err); res.status(200).json(user);
res.status(400).json(err); }
} else { });
res.status(200).json(updatedUser);
}
});
}
}); });
} }
}); });
@ -269,31 +253,20 @@ router.post('/feedback', function(req, res, next) {
router.post('/password_reset_requests', (req, res, next) => { router.post('/password_reset_requests', (req, res, next) => {
const email = req.query.email; const email = req.query.email;
User.findOne({"email": email}).exec((err, user) => { db.User.findOne({where: {"email": email}}).then((user) => {
if (err) { if (user) {
res.status(400).json(err); crypto.randomBytes(16, (ex, buf) => {
user.password_reset_token = buf.toString('hex');
user.save().then(updatedUser => {
mailer.sendMail(email, req.i18n.__("password_reset_subject"), req.i18n.__("password_reset_body"), {action: {
link: config.endpoint + "/password-confirm/" + user.password_reset_token,
name: req.i18n.__("password_reset_action")
}});
res.status(201).json({});
});
});
} else { } else {
if (user) { res.status(404).json({"error": "error_unknown_email"});
if(user.account_type == "email") {
crypto.randomBytes(16, (ex, buf) => {
user.password_reset_token = buf.toString('hex');
user.save((err, updatedUser) => {
if (err) res.status(400).json(err);
else {
mailer.sendMail(email, req.i18n.__("password_reset_subject"), req.i18n.__("password_reset_body"), {action: {
link: config.endpoint + "/password-confirm/" + user.password_reset_token,
name: req.i18n.__("password_reset_action")
}});
res.status(201).json({});
}
});
});
} else {
res.status(404).json({"error": "error_unknown_email"});
}
} else {
res.status(404).json({"error": "error_unknown_email"});
}
} }
}); });
}); });
@ -302,29 +275,25 @@ router.post('/password_reset_requests/:confirm_token/confirm', function(req, res
var password = req.body.password; var password = req.body.password;
User User
.findOne({"password_reset_token": req.params.confirm_token}) .findOne({where: {"password_reset_token": req.params.confirm_token}})
.exec((err, user) => { .then((user) => {
if (err) { if (user) {
res.sendStatus(400); bcrypt.genSalt(10, (err, salt) => {
} else { bcrypt.hash(password, salt, function(err, hash) {
if(user) {
bcrypt.genSalt(10, (err, salt) => {
bcrypt.hash(password, salt, function(err, hash) {
user.password_hash = hash; user.password_hash = hash;
user.password_token = null; user.password_token = null;
user.save(function(err, updatedUser){ user.save(function(err, updatedUser){
if (err) { if (err) {
res.sendStatus(400); res.sendStatus(400);
} else { } else {
res.sendStatus(201); res.sendStatus(201);
} }
});
}); });
}); });
} else { });
res.sendStatus(404); } else {
} res.sendStatus(404);
} }
}); });
}); });

View File

@ -9,7 +9,6 @@ const crypto = require('crypto');
const router = express.Router(); const router = express.Router();
const mailer = require('../helpers/mailer'); const mailer = require('../helpers/mailer');
const _ = require('underscore'); const _ = require('underscore');
const qr = require('qr-image');
router.get('/', (req, res) => { router.get('/', (req, res) => {
res.render('index', { title: 'Spaces' }); res.render('index', { title: 'Spaces' });

169
spacedeck.js Normal file
View File

@ -0,0 +1,169 @@
"use strict";
const db = require('./models/db.js');
require("log-timestamp");
const config = require('config');
const redis = require('./helpers/redis');
const websockets = require('./helpers/websockets');
const http = require('http');
const path = require('path');
const _ = require('underscore');
const favicon = require('serve-favicon');
const logger = require('morgan');
const cookieParser = require('cookie-parser');
const bodyParser = require('body-parser');
const swig = require('swig');
const i18n = require('i18n-2');
const helmet = require('helmet');
const express = require('express');
const app = express();
const serveStatic = require('serve-static');
const isProduction = app.get('env') === 'production';
console.log("Booting Spacedeck Open… (environment: " + app.get('env') + ")");
app.use(logger(isProduction ? 'combined' : 'dev'));
i18n.expressBind(app, {
locales: ["en", "de", "fr"],
defaultLocale: "en",
cookieName: "spacedeck_locale",
devMode: (app.get('env') == 'development')
});
swig.setDefaults({
varControls: ["[[", "]]"] // otherwise it's not compatible with vue.js
});
swig.setFilter('cdn', function(input, idx) {
return input;
});
app.engine('html', swig.renderFile);
app.set('view engine', 'html');
if (isProduction) {
app.set('views', path.join(__dirname, 'build', 'views'));
app.use(favicon(path.join(__dirname, 'build', 'assets', 'images', 'favicon.png')));
app.use(express.static(path.join(__dirname, 'build', 'assets')));
} else {
app.set('views', path.join(__dirname, 'views'));
app.use(favicon(path.join(__dirname, 'public', 'images', 'favicon.png')));
app.use(express.static(path.join(__dirname, 'public')));
}
app.use(bodyParser.json({
limit: '50mb'
}));
app.use(bodyParser.urlencoded({
extended: false,
limit: '50mb'
}));
app.use(cookieParser());
app.use(helmet.frameguard())
app.use(helmet.xssFilter())
app.use(helmet.hsts({
maxAge: 7776000000,
includeSubdomains: true
}))
app.disable('x-powered-by');
app.use(helmet.noSniff())
//app.use(require("./middlewares/error_helpers"));
app.use(require("./middlewares/session"));
//app.use(require("./middlewares/cors"));
app.use(require("./middlewares/i18n"));
app.use("/api", require("./middlewares/api_helpers"));
app.use('/api/spaces/:id', require("./middlewares/space_helpers"));
app.use('/api/spaces/:id/artifacts/:artifact_id', require("./middlewares/artifact_helpers"));
app.use('/api/users', require('./routes/api/users'));
app.use('/api/memberships', require('./routes/api/memberships'));
const spaceRouter = require('./routes/api/spaces');
app.use('/api/spaces', spaceRouter);
spaceRouter.use('/:id/artifacts', require('./routes/api/space_artifacts'));
spaceRouter.use('/:id/memberships', require('./routes/api/space_memberships'));
spaceRouter.use('/:id/messages', require('./routes/api/space_messages'));
spaceRouter.use('/:id/digest', require('./routes/api/space_digest'));
spaceRouter.use('/:id', require('./routes/api/space_exports'));
app.use('/api/sessions', require('./routes/api/sessions'));
//app.use('/api/webgrabber', require('./routes/api/webgrabber'));
app.use('/', require('./routes/root'));
if (config.get('storage_local_path')) {
app.use('/storage', serveStatic(config.get('storage_local_path')+"/"+config.get('storage_bucket'), {
maxAge: 24*3600
}));
}
// catch 404 and forward to error handler
//app.use(require('./middlewares/404'));
if (app.get('env') == 'development') {
app.set('view cache', false);
swig.setDefaults({cache: false});
} else {
app.use(require('./middlewares/500'));
}
module.exports = app;
// CONNECT TO DATABASE
db.init();
// START WEBSERVER
const port = 9666;
const server = http.Server(app).listen(port, () => {
if ("send" in process) {
process.send('online');
}
}).on('listening', () => {
const host = server.address().address;
const port = server.address().port;
console.log('Spacedeck Open listening at http://%s:%s', host, port);
}).on('error', (error) => {
if (error.syscall !== 'listen') {
throw error;
}
const bind = typeof port === 'string' ? 'Pipe ' + port : 'Port ' + port;
switch (error.code) {
case 'EACCES':
console.error(bind + ' requires elevated privileges');
process.exit(1);
break;
case 'EADDRINUSE':
console.error(bind + ' is already in use');
process.exit(1);
break;
default:
throw error;
}
});
websockets.startWebsockets(server);
redis.connectRedis();
/*process.on('message', (message) => {
console.log("Process message:", message);
if (message === 'shutdown') {
console.log("Exiting Spacedeck.");
process.exit(0);
}
});*/

View File

@ -86,15 +86,15 @@
<div class="collapse" v-bind:class="{in:account=='language'}"> <div class="collapse" v-bind:class="{in:account=='language'}">
<div class="modal-section"> <div class="modal-section">
<label class="radio" v-bind:class="{checked <label class="radio" v-bind:class="{checked
: user.preferences.language=='en'}" v-on:click="save_user_language('en')"> : user.prefs_language=='en'}" v-on:click="save_user_language('en')">
<input type="radio" id="user-preferences_language" name="language" value="en"><span>English</span> <input type="radio" id="user-preferences_language" name="language" value="en"><span>English</span>
</label> </label>
<hr/> <hr/>
<label class="radio" v-bind:class="{checked: user.preferences.language=='de'}" v-on:click="save_user_language('de')"> <label class="radio" v-bind:class="{checked: user.prefs_language=='de'}" v-on:click="save_user_language('de')">
<input type="radio" id="user-preferences_language" name="language" value="de"><span>Deutsch</span> <input type="radio" id="user-preferences_language" name="language" value="de"><span>Deutsch</span>
</label> </label>
<hr/> <hr/>
<label class="radio" v-bind:class="{checked: user.preferences.language=='fr'}" v-on:click="save_user_language('fr')"> <label class="radio" v-bind:class="{checked: user.prefs_language=='fr'}" v-on:click="save_user_language('fr')">
<input type="radio" id="user-preferences_language" name="language" value="fr"><span>Français</span> <input type="radio" id="user-preferences_language" name="language" value="fr"><span>Français</span>
</label> </label>
</div> </div>
@ -104,8 +104,8 @@
<div class="modal-section labels-inline"> <div class="modal-section labels-inline">
<div class="form-group"> <div class="form-group">
<label class="checkbox" <label class="checkbox"
v-bind:class="{checked: user.preferences.email_notifications}" v-bind:class="{checked: user.prefs_email_notifications}"
v-on:click="account_save_user_notifications(!user.preferences.email_notifications);"> v-on:click="account_save_user_notifications(!user.prefs_email_notifications);">
<span>[[__('notifications_option_chat')]]</span> <span>[[__('notifications_option_chat')]]</span>
</label> </label>
</div> </div>

View File

@ -77,19 +77,6 @@
</div> </div>
</div> </div>
<div id="mobile-dialog" class="dropdown bottom light center static" v-bind:class="{open:opened_dialog=='mobile'}">
<div class="btn-collapse in">
<button class="btn btn-transparent btn-icon-labeled" v-bind:class="{open:opened_dialog=='mobile'}" v-on:click="open_dialog('mobile')" >
<span class="icon icon-device-mobile"></span>
<span class="icon-label">[[__("mobile")]]</span>
</button>
</div>
<div class="dialog mobile-search">
{% include "./pick-mobile.html" %}
</div>
</div>
<button class="btn btn-divider" v-show="logged_in"></button> <button class="btn btn-divider" v-show="logged_in"></button>
<div class="dropdown bottom light center" v-show="logged_in" v-bind:class="{open:opened_dialog=='background'}"> <div class="dropdown bottom light center" v-show="logged_in" v-bind:class="{open:opened_dialog=='background'}">

View File

@ -13,6 +13,8 @@
<link type="text/css" rel="stylesheet" href="https://fast.fonts.net/cssapi/ee1a3484-4d98-4f9f-9f55-020a7b37f3c5.css"/> <link type="text/css" rel="stylesheet" href="https://fast.fonts.net/cssapi/ee1a3484-4d98-4f9f-9f55-020a7b37f3c5.css"/>
<link rel="stylesheet" href="[[ '/stylesheets/style.css' | cdn ]]"> <link rel="stylesheet" href="[[ '/stylesheets/style.css' | cdn ]]">
<script>if (typeof module === 'object') {window.module = module; module = undefined;}</script>
<script src="//cdnjs.cloudflare.com/ajax/libs/twemoji/1.3.2/twemoji.min.js"></script> <script src="//cdnjs.cloudflare.com/ajax/libs/twemoji/1.3.2/twemoji.min.js"></script>
<script> <script>
@ -83,6 +85,8 @@
<script minify src="/javascripts/spacedeck_directives.js"></script> <script minify src="/javascripts/spacedeck_directives.js"></script>
<script minify src="/javascripts/spacedeck_vue.js"></script> <script minify src="/javascripts/spacedeck_vue.js"></script>
{% endif %} {% endif %}
<script>if (window.module) module = window.module;</script>
</head> </head>
<body id="main" v-bind:class="{'present-mode':present_mode,'modal-open':active_modal}" v-on:click="handle_body_click($event)"> <body id="main" v-bind:class="{'present-mode':present_mode,'modal-open':active_modal}" v-on:click="handle_body_click($event)">