Unverified Commit ebac854d authored by mntmn's avatar mntmn Committed by GitHub
Browse files

Port Backend to SQLite/Sequelize (removes MongoDB), Support Electron (#14)

* The MongoDB/Mongoose data storage is removed in favor of Sequelize. This abstracts over SQLite or RDBMs like PostgreSQL and MSSQL. The default is SQLite, which significantly simplifies deployments in end-user environments.

* As Spacedeck now has no more mandatory server dependencies, we can wrap it in Electron and ship it as a desktop application.

* Removes docker-compose.yml

* First version of import UI
parent 8e0bc69a
Showing with 684 additions and 1288 deletions
+684 -1288
...@@ -21,7 +21,7 @@ function vec2_angle(v) { ...@@ -21,7 +21,7 @@ function vec2_angle(v) {
} }
function render_vector_drawing(a, padding) { function render_vector_drawing(a, padding) {
var shape = a.style.shape || ""; var shape = a.shape || "";
var path = []; var path = [];
var p = a.control_points[0]; var p = a.control_points[0];
...@@ -48,8 +48,8 @@ function render_vector_drawing(a, padding) { ...@@ -48,8 +48,8 @@ function render_vector_drawing(a, padding) {
var d = "M" + (cps.dx + padding) + "," + (cps.dy + padding) + " Q" + (scaledMiddlePoint.dx + padding) + "," + (scaledMiddlePoint.dy + padding) + " " + (cpe.dx + padding) + "," + (cpe.dy + padding); var d = "M" + (cps.dx + padding) + "," + (cps.dy + padding) + " Q" + (scaledMiddlePoint.dx + padding) + "," + (scaledMiddlePoint.dy + padding) + " " + (cpe.dx + padding) + "," + (cpe.dy + padding);
var tip = "<defs><marker id='ae" + markerId + "' refX=\"0.1\" refY=\"3\" markerWidth=\"3\" markerHeight=\"6\" orient=\"auto\">"; var tip = "<defs><marker id='ae" + markerId + "' refX=\"0.1\" refY=\"3\" markerWidth=\"3\" markerHeight=\"6\" orient=\"auto\">";
tip += "<path d=\"M-3,0 V6 L3,3 Z\" fill=\""+a.style.stroke_color+"\" stroke-width=\"0\"/></marker></defs>"; tip += "<path d=\"M-3,0 V6 L3,3 Z\" fill=\""+a.stroke_color+"\" stroke-width=\"0\"/></marker></defs>";
var svg = tip + "<path d='" + d + "' style='stroke-width:" + a.style.stroke + ";' marker-end='url(#ae" + markerId + ")'/>"; var svg = tip + "<path d='" + d + "' style='stroke-width:" + a.stroke + ";' marker-end='url(#ae" + markerId + ")'/>";
return svg; return svg;
} }
...@@ -237,11 +237,11 @@ function render_vector_rect(xradius,yradius,offset) { ...@@ -237,11 +237,11 @@ function render_vector_rect(xradius,yradius,offset) {
} }
function render_vector_shape(a) { function render_vector_shape(a) {
var stroke = parseInt(a.style.stroke) + 4; var stroke = parseInt(a.stroke) + 4;
var offset = stroke / 2; var offset = stroke / 2;
var xr = (a.board.w-stroke) / 2; var xr = (a.w-stroke) / 2;
var yr = (a.board.h-stroke) / 2; var yr = (a.h-stroke) / 2;
var shape_renderers = { var shape_renderers = {
ellipse: function() { return render_vector_ellipse(xr, yr, offset); }, ellipse: function() { return render_vector_ellipse(xr, yr, offset); },
...@@ -258,7 +258,7 @@ function render_vector_shape(a) { ...@@ -258,7 +258,7 @@ function render_vector_shape(a) {
cloud: function() { return render_vector_cloud(xr, yr, offset); }, cloud: function() { return render_vector_cloud(xr, yr, offset); },
} }
var render_func = shape_renderers[a.style.shape]; var render_func = shape_renderers[a.shape];
if (!render_func) return ""; if (!render_func) return "";
......
"use strict"; "use strict";
var config = require('config'); var config = require('config');
require('../../models/schema');
var fs = require('fs');
var _ = require("underscore");
var mongoose = require("mongoose");
var async = require('async'); var async = require('async');
var archiver = require('archiver');
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 express = require('express'); var express = require('express');
var router = express.Router(); var router = express.Router();
var userMapping = { '_id': 1, 'nickname': 1, 'email': 1}; const db = require('../../models/db');
var spaceMapping = { '_id': 1, name: 1}; const Sequelize = require('sequelize');
const Op = Sequelize.Op;
const uuidv4 = require('uuid/v4');
router.get('/:membership_id/accept', function(req, res, next) { router.get('/:membership_id/accept', function(req, res, next) {
if (req.user) { if (req.user) {
Membership.findOne({ db.Membership.findOne({where:{
_id: req.params.membership_id, _id: req.params.membership_id,
state: "pending", code: req.query.code
code: req.query.code, }, include: ['space']}).then((mem) => {
user: { "$exists": false } if (mem) {
}).populate('space').exec((err,mem) => { if (!mem.user) {
if (err) res.sendStatus(400); mem.state = "active";
else { mem.user_id = req.user._id;
if (mem) {
if(!mem.user) { mem.save().then(function() {
mem.code = null; res.status(200).json(mem);
mem.state = "active"; });
mem.user = req.user;
mem.save(function(err){
if (err) res.status(400).json(err);
else {
console.log(mem);
res.status(200).json(mem);
}
});
} else {
res.status(400).json({"error": "already_used"});
}
} else { } else {
res.status(404).json({"error": "not found"}); res.status(200).json(mem);
} }
} else {
res.status(404).json({"error": "not found"});
} }
}); });
} else { } else {
......
"use strict"; "use strict";
var config = require('config'); var config = require('config');
require('../../models/schema'); const db = require('../../models/db');
var bcrypt = require('bcryptjs'); var bcrypt = require('bcryptjs');
var crypo = require('crypto'); var crypto = require('crypto');
var URL = require('url').URL; var URL = require('url').URL;
var express = require('express'); var express = require('express');
...@@ -12,68 +12,64 @@ var router = express.Router(); ...@@ -12,68 +12,64 @@ var router = express.Router();
router.post('/', function(req, res) { router.post('/', function(req, res) {
var data = req.body; var data = req.body;
if (data.email && data.password) { if (!data.email || !data.password) {
var email = req.body.email.toLowerCase(); res.status(400).json({});
var password = req.body["password"]; return;
}
User.find({email: email, account_type: "email"}, (function (err, users) {
if (err) { var email = req.body.email.toLowerCase();
res.status(400).json({"error":"session.users"}); var password = req.body["password"];
} else {
if (users.length == 1) {
var user = users[0];
if (bcrypt.compareSync(password, user.password_hash)) {
crypo.randomBytes(48, function(ex, buf) {
var token = buf.toString('hex');
var session = {
token: token,
ip: req.ip,
device: "web",
created_at: new Date()
};
if (!user.sessions)
user.sessions = [];
user.sessions.push(session); db.User.findOne({where: {email: email}})
.error(err => {
res.sendStatus(404);
//res.status(400).json({"error":"session.users"});
})
.then(user => {
console.log("!!! user: ",user.password_hash);
if (bcrypt.compareSync(password, user.password_hash)) {
crypto.randomBytes(48, function(ex, buf) {
var token = buf.toString('hex');
console.log("!!! token: ",token);
user.save(function(err, result) { var session = {
if (err) console.error("Error saving user:",err); user_id: user._id,
token: token,
var domain = (process.env.NODE_ENV == "production") ? new URL(config.get('endpoint')).hostname : "localhost"; ip: req.ip,
device: "web",
created_at: new Date()
};
res.cookie('sdsession', token, { domain: domain, httpOnly: true }); db.Session.create(session)
res.status(201).json(session); .error(err => {
}); console.error("Error creating Session:",err);
res.sendStatus(500);
})
.then(() => {
var domain = (process.env.NODE_ENV == "production") ? new URL(config.get('endpoint')).hostname : "localhost";
res.cookie('sdsession', token, { domain: domain, httpOnly: true });
res.status(201).json(session);
}); });
}else{ });
res.sendStatus(403); } else {
} res.sendStatus(403);
} else {
res.sendStatus(404);
}
} }
})); });
} else {
res.status(400).json({});
}
}); });
router.delete('/current', function(req, res, next) { router.delete('/current', function(req, res, next) {
if (req.user) { if (req.user) {
var user = req.user; /*var user = req.user;
var newSessions = user.sessions.filter( function(session){ var newSessions = user.sessions.filter( function(session){
return session.token != req.token; return session.token != req.token;
}); });*/
user.sessions = newSessions; //user.sessions = newSessions;
user.save(function(err, result) { //user.save(function(err, result) {
var domain = new URL(config.get('endpoint')).hostname; var domain = new URL(config.get('endpoint')).hostname;
res.clearCookie('sdsession', { domain: domain }); res.clearCookie('sdsession', { domain: domain });
res.sendStatus(204); res.sendStatus(204);
}); //});
} else { } else {
res.sendStatus(404); res.sendStatus(404);
} }
......
"use strict"; "use strict";
var config = require('config'); var config = require('config');
require('../../models/schema');
const os = require('os');
const db = require('../../models/db');
const Sequelize = require('sequelize');
const Op = Sequelize.Op;
const uuidv4 = require('uuid/v4');
var payloadConverter = require('../../helpers/artifact_converter'); var payloadConverter = require('../../helpers/artifact_converter');
var redis = require('../../helpers/redis'); var redis = require('../../helpers/redis');
...@@ -9,13 +14,11 @@ var redis = require('../../helpers/redis'); ...@@ -9,13 +14,11 @@ var redis = require('../../helpers/redis');
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");
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');
...@@ -46,15 +49,24 @@ var roleMapping = { ...@@ -46,15 +49,24 @@ var roleMapping = {
// ARTIFACTS // ARTIFACTS
router.get('/', (req, res) => { router.get('/', (req, res) => {
Artifact.find({ db.Artifact.findAll({where: {
space_id: req.space._id space_id: req.space._id
}).exec((err, artifacts) => { }}).then(artifacts => {
async.map(artifacts, (a, cb) => { async.map(artifacts, (a, cb) => {
a = a.toObject(); //a = a.toObject(); TODO
if (a.control_points) {
a.control_points = JSON.parse(a.control_points);
}
if (a.payload_alternatives) {
a.payload_alternatives = JSON.parse(a.payload_alternatives);
}
if (a.user_id) { if (a.user_id) {
User.findOne({ // FIXME JOIN
/*User.findOne({where: {
"_id": a.user_id "_id": a.user_id
}).select({ }}).select({
"_id": 1, "_id": 1,
"nickname": 1, "nickname": 1,
"email": 1 "email": 1
...@@ -63,7 +75,8 @@ router.get('/', (req, res) => { ...@@ -63,7 +75,8 @@ router.get('/', (req, res) => {
a['user'] = user.toObject(); a['user'] = user.toObject();
} }
cb(err, a); cb(err, a);
}); });*/
cb(null, a);
} else { } else {
cb(null, a); cb(null, a);
} }
...@@ -81,9 +94,8 @@ router.post('/', function(req, res, next) { ...@@ -81,9 +94,8 @@ router.post('/', function(req, res, next) {
attrs['space_id'] = req.space._id; attrs['space_id'] = req.space._id;
var artifact = new Artifact(attrs); var artifact = attrs;
artifact._id = uuidv4();
artifact.created_from_ip = req['real_ip'];
if (req.user) { if (req.user) {
artifact.user_id = req.user._id; artifact.user_id = req.user._id;
...@@ -92,23 +104,18 @@ router.post('/', function(req, res, next) { ...@@ -92,23 +104,18 @@ router.post('/', function(req, res, next) {
artifact.last_update_editor_name = req.editor_name; artifact.last_update_editor_name = req.editor_name;
} }
if (req.spaceRole == "editor"  ||  req.spaceRole == "admin") { db.packArtifact(artifact);
artifact.save(function(err) {
if (err) res.status(400).json(err); if (req.spaceRole == "editor" || req.spaceRole == "admin") {
else { db.Artifact.create(artifact).then(() => {
Space.update({ //if (err) res.status(400).json(err);
_id: req.space._id db.unpackArtifact(artifact);
}, { db.Space.update({ updated_at: new Date() }, {where: {_id: req.space._id}});
"$set": { res.distributeCreate("Artifact", artifact);
updated_at: new Date()
}
});
res.distributeCreate("Artifact", artifact);
}
}); });
} else { } else {
res.status(401).json({ res.status(401).json({
"error": "no access" "error": "Access denied"
}); });
} }
}); });
...@@ -118,7 +125,8 @@ router.post('/:artifact_id/payload', function(req, res, next) { ...@@ -118,7 +125,8 @@ router.post('/:artifact_id/payload', function(req, res, next) {
var a = req.artifact; var a = req.artifact;
var fileName = (req.query.filename || "upload.bin").replace(/[^a-zA-Z0-9_\-\.]/g, ''); var fileName = (req.query.filename || "upload.bin").replace(/[^a-zA-Z0-9_\-\.]/g, '');
var localFilePath = "/tmp/" + fileName;
var localFilePath = os.tmpdir() + "/" + fileName;
var writeStream = fs.createWriteStream(localFilePath); var writeStream = fs.createWriteStream(localFilePath);
var stream = req.pipe(writeStream); var stream = req.pipe(writeStream);
...@@ -132,13 +140,7 @@ router.post('/:artifact_id/payload', function(req, res, next) { ...@@ -132,13 +140,7 @@ router.post('/:artifact_id/payload', function(req, res, next) {
payloadConverter.convert(a, fileName, localFilePath, function(error, artifact) { payloadConverter.convert(a, fileName, localFilePath, function(error, artifact) {
if (error) res.status(400).json(error); if (error) res.status(400).json(error);
else { else {
Space.update({ db.Space.update({ updated_at: new Date() }, {where: {_id: req.space._id}});
_id: req.space._id
}, {
"$set": {
updated_at: new Date()
}
});
res.distributeUpdate("Artifact", artifact); res.distributeUpdate("Artifact", artifact);
} }
}, progress_callback); }, progress_callback);
...@@ -161,42 +163,23 @@ router.put('/:artifact_id', function(req, res, next) { ...@@ -161,42 +163,23 @@ router.put('/:artifact_id', function(req, res, next) {
} else { } else {
newAttr.last_update_editor_name = req.editor_name; newAttr.last_update_editor_name = req.editor_name;
} }
db.packArtifact(newAttr);
Artifact.findOneAndUpdate({ db.Artifact.update(newAttr, { where: {
"_id": a._id "_id": a._id
}, { }}).then(rows => {
"$set": newAttr db.unpackArtifact(newAttr);
}, { db.Space.update({ updated_at: new Date() }, {where: {_id: req.space._id} });
"new": true res.distributeUpdate("Artifact", newAttr);
}, function(err, artifact) {
if (err) res.status(400).json(err);
else {
Space.update({
_id: req.space._id
}, {
"$set": {
updated_at: new Date()
}
});
res.distributeUpdate("Artifact", artifact);
}
}); });
}); });
router.delete('/:artifact_id', function(req, res, next) { router.delete('/:artifact_id', function(req, res, next) {
var artifact = req.artifact; var artifact = req.artifact;
artifact.remove(function(err) { db.Artifact.destroy({where: { "_id": artifact._id}}).then(() => {
if (err) res.status(400).json(err); db.Space.update({ updated_at: new Date() }, {where: {_id: req.space._id} });
else { res.distributeDelete("Artifact", artifact);
Space.update({
_id: req.space._id
}, {
"$set": {
updated_at: new Date()
}
});
res.distributeDelete("Artifact", artifact);
}
}); });
}); });
......
"use strict"; "use strict";
var config = require('config'); var config = require('config');
require('../../models/schema'); 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');
...@@ -40,6 +38,12 @@ var roleMapping = { ...@@ -40,6 +38,12 @@ var roleMapping = {
}; };
router.get('/', function(req, res, next) { router.get('/', function(req, res, next) {
res.status(200).json([]);
return;
// FIXME TODO
var showActionForSpaces = function(err, spaceIds) { var showActionForSpaces = function(err, spaceIds) {
var userMapping = { var userMapping = {
'_id': 1, '_id': 1,
......
"use strict"; "use strict";
var config = require('config'); var config = require('config');
require('../../models/schema'); 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'); ...@@ -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');
...@@ -49,26 +46,18 @@ var roleMapping = { ...@@ -49,26 +46,18 @@ var roleMapping = {
router.get('/png', function(req, res, next) { router.get('/png', function(req, res, next) {
var triggered = new Date(); var triggered = new Date();
var s3_filename = "s" + req.space._id + "/" + "thumb_" + triggered.getTime() + ".jpg"; var s3_filename = "s" + req.space._id + "/" + "thumb_" + triggered.getTime() + ".jpg";
if (!req.space.thumbnail_updated_at || req.space.thumbnail_updated_at < req.space.updated_at || !req.space.thumbnail_url) { if (!req.space.thumbnail_updated_at || req.space.thumbnail_updated_at < req.space.updated_at || !req.space.thumbnail_url) {
db.Space.update({ thumbnail_updated_at: triggered }, {where : {"_id": req.space._id }});
Space.update({
"_id": req.space._id
}, {
"$set": {
thumbnail_updated_at: triggered
}
}, function(a, b, c) {});
phantom.takeScreenshot(req.space, "png", phantom.takeScreenshot(req.space, "png",
function(local_path) { function(local_path) {
var localResizedFilePath = local_path + ".thumb.jpg"; var localResizedFilePath = local_path + ".thumb.jpg";
gm(local_path).resize(640, 480).quality(70.0).autoOrient().write(localResizedFilePath, function(err) { gm(local_path).resize(640, 480).quality(70.0).autoOrient().write(localResizedFilePath, function(err) {
if (err) { if (err) {
console.error("screenshot resize error: ", err); console.error("[space screenshot] resize error: ", err);
res.status(500).send("Error taking screenshot."); res.status(500).send("Error taking screenshot.");
return; return;
} }
...@@ -76,22 +65,15 @@ router.get('/png', function(req, res, next) { ...@@ -76,22 +65,15 @@ router.get('/png', function(req, res, next) {
uploader.uploadFile(s3_filename, "image/jpeg", localResizedFilePath, function(err, thumbnailUrl) { uploader.uploadFile(s3_filename, "image/jpeg", localResizedFilePath, function(err, thumbnailUrl) {
if (err) { if (err) {
console.error("screenshot s3 upload error. filename: " + s3_filename + " details: ", err); console.error("[space screenshot] upload error. filename: " + s3_filename + " details: ", err);
res.status(500).send("Error uploading screenshot."); res.status(500).send("Error uploading screenshot.");
return; return;
} }
var oldUrl = req.space.thumbnail_url; var oldUrl = req.space.thumbnail_url;
Space.update({ db.Space.update({ thumbnail_url: thumbnailUrl }, {where : {"_id": req.space._id }}).then(() => {
"_id": req.space._id
}, {
"$set": {
thumbnail_url: thumbnailUrl
}
}, function(a, b, c) {
res.redirect(thumbnailUrl); res.redirect(thumbnailUrl);
try { try {
if (oldUrl) { if (oldUrl) {
var oldPath = url.parse(oldUrl).pathname; var oldPath = url.parse(oldUrl).pathname;
...@@ -125,77 +107,6 @@ function make_export_filename(space, extension) { ...@@ -125,77 +107,6 @@ function make_export_filename(space, extension) {
return space.name.replace(/[^\w]/g, '') + "-" + space._id + "-" + moment().format("YYYYMMDD-HH-mm-ss") + "." + extension; return space.name.replace(/[^\w]/g, '') + "-" + space._id + "-" + moment().format("YYYYMMDD-HH-mm-ss") + "." + extension;
} }
router.get('/list', function(req, res, next) {
if (req.user) {
if (req.spaceRole == "admin" ||  req.spaceRole == "editor") {
if (req.space.space_type == "space") {
Artifact.find({
space_id: req.space._id
}).exec(function(err, artifacts) {
async.map(artifacts, function(a, cb) {
if (a.user_id) {
User.findOne({
"_id": a.user_id
}).exec(function(err, user) {
a.user = user;
if (a.last_update_user_id) {
User.findOne({
"_id": a.last_update_user_id
}).exec(function(err, updateUser) {
a.update_user = updateUser;
cb(null, a);
});
} else {
cb(null, a);
}
});
} else {
cb(null, a);
}
}, function(err, mappedArtifacts) {
req.space.artifacts = mappedArtifacts.map(function(a) {
a.description = sanitizeHtml(a.description, {
allowedTags: [],
allowedAttributes: []
});
if (a.payload_uri) {
var parsed = url.parse(a.payload_uri);
var fileName = path.basename(parsed.pathname) || "file.bin";
a.filename = fileName;
}
return a;
});
res.render('artifact_list', {
space: req.space
});
});
});
} else {
Space.getRecursiveSubspacesForSpace(req.space, (err, subspaces) => {
res.render('space_list', {
subspaces: subspaces.map((s) => {
s.ae_link = config.endpoint + '/s/' + s.edit_hash + (s.edit_slug ? ('-'+s.edit_slug) : '')
return s;
}),
space: req.space
});
});
}
} else {
res.sendStatus(403);
}
} else {
res.sendStatus(403);
}
});
router.get('/pdf', function(req, res, next) { router.get('/pdf', function(req, res, next) {
var s3_filename = make_export_filename(req.space, "pdf"); var s3_filename = make_export_filename(req.space, "pdf");
...@@ -329,36 +240,14 @@ router.get('/zip', function(req, res, next) { ...@@ -329,36 +240,14 @@ router.get('/zip', function(req, res, next) {
}); });
router.get('/html', function(req, res) { router.get('/html', function(req, res) {
Artifact.find({ console.log("!!!!! hello ");
db.Artifact.findAll({where: {
space_id: req.space._id space_id: req.space._id
}, function(err, artifacts) { }}).then(function(artifacts) {
var space = req.space; var space = req.space;
res.send(space_render.render_space_as_html(space, artifacts)); res.send(space_render.render_space_as_html(space, artifacts));
}); });
}); });
router.get('/path', (req, res) => {
// build up a breadcrumb trail (path)
var path = [];
var buildPath = (space) => {
if (space.parent_space_id) {
Space.findOne({
"_id": space.parent_space_id
}, (err, parentSpace) => {
if (space._id == parentSpace._id) {
console.log("error: circular parent reference for space " + space._id);
res.send("error: circular reference");
} else {
path.push(parentSpace);
buildPath(parentSpace);
}
});
} else {
// reached the top
res.json(path.reverse());
}
}
buildPath(req.space);
});
module.exports = router; module.exports = router;
"use strict"; "use strict";
var config = require('config'); var config = require('config');
require('../../models/schema'); const db = require('../../models/db');
const Sequelize = require('sequelize');
const Op = Sequelize.Op;
const uuidv4 = require('uuid/v4');
var redis = require('../../helpers/redis'); var redis = require('../../helpers/redis');
var mailer = require('../../helpers/mailer'); var mailer = require('../../helpers/mailer');
var uploader = require('../../helpers/uploader');
var space_render = require('../../helpers/space-render');
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 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 qr = require('qr-image');
var glob = require('glob'); var glob = require('glob');
var gm = require('gm'); var crypto = require('crypto');
var express = require('express'); var express = require('express');
var router = express.Router({mergeParams: true}); var router = express.Router({mergeParams: true});
// JSON MAPPINGS
var userMapping = {
_id: 1,
nickname: 1,
email: 1,
avatar_thumb_uri: 1
};
var spaceMapping = {
_id: 1,
name: 1,
thumbnail_url: 1
};
var roleMapping = {
"none": 0,
"viewer": 1,
"editor": 2,
"admin": 3
}
router.get('/', function(req, res, next) { router.get('/', function(req, res, next) {
Membership db.Membership
.find({ .findAll({where: {
space: req.space._id space_id: req.space._id
}) }, include: ['user']})
.populate("user") .then(memberships => {
.exec(function(err, memberships) {
res.status(200).json(memberships); res.status(200).json(memberships);
}); });
}); });
...@@ -59,52 +33,51 @@ router.get('/', function(req, res, next) { ...@@ -59,52 +33,51 @@ router.get('/', function(req, res, next) {
router.post('/', function(req, res, next) { router.post('/', function(req, res, next) {
if (req.spaceRole == "admin") { if (req.spaceRole == "admin") {
var attrs = req.body; var attrs = req.body;
attrs['space'] = req.space._id; attrs.space_id = req.space._id;
attrs['state'] = "pending"; attrs.state = "pending";
var membership = new Membership(attrs); attrs._id = uuidv4();
var membership = attrs;
var msg = attrs.personal_message; var msg = attrs.personal_message;
if (membership.email_invited != req.user.email) { if (membership.email_invited != req.user.email) {
User.findOne({ db.User.findOne({where:{
"email": membership.email_invited "email": membership.email_invited
}, function(err, user) { }}).then(function(user) {
if (user) { if (user) {
membership.user = user; membership.user_id = user._id;
membership.state = "active"; membership.state = "active";
} else { } else {
membership.code = crypto.randomBytes(64).toString('hex').substring(0, 12); membership.code = crypto.randomBytes(64).toString('hex').substring(0, 12);
} }
membership.save(function(err) { db.Membership.create(membership).then(function() {
if (err) res.sendStatus(400); var accept_link = config.endpoint + "/accept/" + membership._id + "?code=" + membership.code;
else {
var accept_link = config.endpoint + "/accept/" + membership._id + "?code=" + membership.code; if (user) {
accept_link = config.endpoint + "/" + req.space.space_type + "s/" + req.space._id;
if (user) {
accept_link = config.endpoint + "/" + req.space.space_type + "s/" + req.space._id;
}
var openText = req.i18n.__("space_invite_membership_action");
if (user) {
req.i18n.__("open");
}
const name = req.user.nickname || req.user.email
const subject = (req.space.space_type == "space") ? req.i18n.__("space_invite_membership_subject", name, req.space.name) : req.i18n.__("folder_invite_membership_subject", req.user.nickname, req.space.name)
const body = (req.space.space_type == "space") ? req.i18n.__("space_invite_membership_body", name, req.space.name) : req.i18n.__("folder_invite_membership_body", req.user.nickname, req.space.name)
mailer.sendMail(
membership.email_invited, subject, body, {
messsage: msg,
action: {
link: accept_link,
name: openText
}
});
res.status(201).json(membership);
} }
var openText = req.i18n.__("space_invite_membership_action");
if (user) {
req.i18n.__("open");
}
const name = req.user.nickname || req.user.email
const subject = (req.space.space_type == "space") ? req.i18n.__("space_invite_membership_subject", name, req.space.name) : req.i18n.__("folder_invite_membership_subject", req.user.nickname, req.space.name)
const body = (req.space.space_type == "space") ? req.i18n.__("space_invite_membership_body", name, req.space.name) : req.i18n.__("folder_invite_membership_body", req.user.nickname, req.space.name)
mailer.sendMail(
membership.email_invited, subject, body, {
messsage: msg,
action: {
link: accept_link,
name: openText
}
});
res.status(201).json(membership);
}); });
}); });
...@@ -125,21 +98,15 @@ router.post('/', function(req, res, next) { ...@@ -125,21 +98,15 @@ router.post('/', function(req, res, next) {
router.put('/:membership_id', function(req, res, next) { router.put('/:membership_id', function(req, res, next) {
if (req.user) { if (req.user) {
if (req.spaceRole == "admin") { if (req.spaceRole == "admin") {
Membership.findOne({ db.Membership.findOne({ where: {
_id: req.params.membership_id _id: req.params.membership_id
}, function(err, mem) { }}).then(function(mem) {
if (err) res.sendStatus(400); if (mem) {
else { var attrs = req.body;
if (mem) { mem.role = attrs.role;
var attrs = req.body; mem.save(function() {
mem.role = attrs.role; res.status(201).json(mem);
mem.save(function(err) { });
if (err) res.sendStatus(400);
else {
res.status(201).json(mem);
}
});
}
} }
}); });
} else { } else {
...@@ -152,20 +119,12 @@ router.put('/:membership_id', function(req, res, next) { ...@@ -152,20 +119,12 @@ router.put('/:membership_id', function(req, res, next) {
router.delete('/:membership_id', function(req, res, next) { router.delete('/:membership_id', function(req, res, next) {
if (req.user) { if (req.user) {
Membership.findOne({ db.Membership.findOne({ where: {
_id: req.params.membership_id _id: req.params.membership_id
}, function(err, mem) { }}).then(function(mem) {
if (err) res.sendStatus(400); mem.destroy().then(function() {
else { res.sendStatus(204);
mem.remove(function(err) { });
if (err) {
res.status(400).json(err);
} else {
// FIXME might need to delete the user?
res.sendStatus(204);
}
});
}
}); });
} else { } else {
res.sendStatus(403); res.sendStatus(403);
......
"use strict"; "use strict";
var config = require('config'); var config = require('config');
require('../../models/schema'); const db = require('../../models/db');
const Sequelize = require('sequelize');
const Op = Sequelize.Op;
const uuidv4 = require('uuid/v4');
var redis = require('../../helpers/redis'); var redis = require('../../helpers/redis');
var mailer = require('../../helpers/mailer'); var mailer = require('../../helpers/mailer');
...@@ -11,15 +14,12 @@ var phantom = require('../../helpers/phantom'); ...@@ -11,15 +14,12 @@ 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");
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 express = require('express'); var express = require('express');
var router = express.Router({mergeParams: true}); var router = express.Router({mergeParams: true});
...@@ -49,90 +49,44 @@ var roleMapping = { ...@@ -49,90 +49,44 @@ var roleMapping = {
// MESSAGES // MESSAGES
router.get('/', function(req, res, next) { router.get('/', function(req, res, next) {
Message.find({ db.Message.findAll({where:{
space: req.space._id space_id: req.space._id
}).populate('user', userMapping).exec(function(err, messages) { }, include: ['user']})
res.status(200).json(messages); .then(function(messages) {
}); res.status(200).json(messages);
});
}); });
router.post('/', function(req, res, next) { router.post('/', function(req, res, next) {
var attrs = req.body; var attrs = req.body;
attrs.space = req.space; attrs.space_id = req.space._id;
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;
} }
var msg = new Message(attrs); var msg = attrs;
msg.save(function(err) { msg._id = uuidv4();
if (err) res.status(400).json(erra);
else {
if (msg.message.length <= 1) return;
Membership
.find({
space: req.space,
user: {
"$exists": true
}
})
.populate('user')
.exec(function(err, memberships) {
var users = memberships.map(function(m) {
return m.user;
});
users.forEach((user) => {
if (user.preferences.email_notifications) {
redis.isOnlineInSpace(user, req.space, function(err, online) {
if (!online) {
var nickname = msg.editor_name;
if (req.user) {
nickname = req.user.nickname;
}
mailer.sendMail(
user.email,
req.i18n.__("space_message_subject", req.space.name),
req.i18n.__("space_message_body", nickname, req.space.name), {
message: msg.message,
action: {
link: config.endpoint + "/spaces/" + req.space._id.toString(),
name: req.i18n.__("open")
}
});
} else {
console.log("not sending message to user: is online.");
}
});
} else {
console.log("not sending message to user: is disabled notifications.");
}
});
});
res.distributeCreate("Message", msg); db.Message.create(msg).then(function() {
} if (msg.message.length <= 1) return;
// TODO reimplement notifications
res.distributeCreate("Message", msg);
}); });
}); });
router.delete('/:message_id', function(req, res, next) { router.delete('/:message_id', function(req, res, next) {
Message.findOne({ db.Message.findOne({where:{
"_id": req.params.message_id "_id": req.params.message_id
}, function(err, msg) { }}).then(function(msg) {
if (!msg) { if (!msg) {
res.sendStatus(404); res.sendStatus(404);
} else { } else {
msg.remove(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);
}
}
}); });
} }
}); });
......
"use strict"; "use strict";
var config = require('config'); var config = require('config');
require('../../models/schema'); const db = require('../../models/db');
const Sequelize = require('sequelize');
const Op = Sequelize.Op;
const uuidv4 = require('uuid/v4');
var redis = require('../../helpers/redis'); var redis = require('../../helpers/redis');
var mailer = require('../../helpers/mailer'); var mailer = require('../../helpers/mailer');
...@@ -14,13 +17,10 @@ var slug = require('slug'); ...@@ -14,13 +17,10 @@ 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 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');
const exec = require('child_process'); const exec = require('child_process');
...@@ -48,15 +48,14 @@ router.get('/', function(req, res, next) { ...@@ -48,15 +48,14 @@ router.get('/', function(req, res, next) {
}); });
} else { } else {
if (req.query.writablefolders) { if (req.query.writablefolders) {
Membership.find({ db.Membership.find({where: {
user: req.user._id user_id: req.user._id
}, (err, memberships) => { }}, (memberships) => {
var validMemberships = memberships.filter((m) => { var validMemberships = memberships.filter((m) => {
if (!m.space || (m.space == "undefined")) if (!m.space_id || (m.space_id == "undefined"))
return false; return false;
else return true;
return mongoose.Types.ObjectId.isValid(m.space.toString());
}); });
var editorMemberships = validMemberships.filter((m) => { var editorMemberships = validMemberships.filter((m) => {
...@@ -64,9 +63,10 @@ router.get('/', function(req, res, next) { ...@@ -64,9 +63,10 @@ router.get('/', function(req, res, next) {
}); });
var spaceIds = editorMemberships.map(function(m) { var spaceIds = editorMemberships.map(function(m) {
return new mongoose.Types.ObjectId(m.space); 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) { ...@@ -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,75 +102,67 @@ router.get('/', function(req, res, next) { ...@@ -104,75 +102,67 @@ 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) {
Membership.find({ db.Membership.findAll({where:{
user: req.user._id user_id: req.user._id
}, function(err, memberships) { }}).then(memberships => {
var validMemberships = memberships.filter(function(m) { var validMemberships = memberships.filter(function(m) {
if (!m.space || (m.space == "undefined")) if (!m.space_id || (m.space_id == "undefined"))
return false; return false;
else else
return mongoose.Types.ObjectId.isValid(m.space.toString()); return true;
}); });
var spaceIds = validMemberships.map(function(m) { var spaceIds = validMemberships.map(function(m) {
return new mongoose.Types.ObjectId(m.space); return m.space_id;
}); });
var q = { // TODO FIXME port
"$or": [{"creator": req.user._id}, var q = { where: {
{"_id": {"$in": spaceIds}}, [Op.or]: [{"creator_id": req.user._id},
{"parent_space_id": {"$in": spaceIds}}], {"_id": {[Op.in]: spaceIds}},
name: new RegExp(req.query.search, "i") {"parent_space_id": {[Op.in]: spaceIds}}],
}; name: {[Op.like]: "%"+req.query.search+"%"}
}, include: ['creator']};
Space
.find(q) db.Space
.populate('creator', userMapping) .findAll(q)
.exec(function(err, 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);
}); });
}); });
} else if (req.query.parent_space_id && req.query.parent_space_id != req.user.home_folder_id) { } else if (req.query.parent_space_id && req.query.parent_space_id != req.user.home_folder_id) {
Space db.Space
.findOne({ .findOne({where: {
_id: req.query.parent_space_id _id: req.query.parent_space_id
}) }})
.populate('creator', userMapping) //.populate('creator', userMapping)
.exec(function(err, space) { .then(function(space) {
if (space) { if (space) {
Space.roleInSpace(space, req.user, function(err, 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";
} }
} }
if (role != "none") { if (role != "none") {
Space db.Space
.find({ .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) {
.exec(function(err, spaces) {
res.status(200).json(spaces); res.status(200).json(spaces);
}); });
} else { } else {
...@@ -185,41 +175,39 @@ router.get('/', function(req, res, next) { ...@@ -185,41 +175,39 @@ router.get('/', function(req, res, next) {
}); });
} else { } else {
Membership.find({ db.Membership.findAll({ where: {
user: req.user._id user_id: req.user._id
}, function(err, memberships) { }}).then(memberships => {
if (!memberships) memberships = [];
var validMemberships = memberships.filter(function(m) { var validMemberships = memberships.filter(function(m) {
if (!m.space || (m.space == "undefined")) if (!m.space_id || (m.space_id == "undefined"))
return false; return false;
else
return mongoose.Types.ObjectId.isValid(m.space.toString());
}); });
var spaceIds = validMemberships.map(function(m) { var spaceIds = validMemberships.map(function(m) {
return new mongoose.Types.ObjectId(m.space); return m.space_id;
}); });
var q = { var q = {
"$or": [{ [Op.or]: [{
"creator": req.user._id, "creator_id": req.user._id,
"parent_space_id": req.user.home_folder_id "parent_space_id": req.user.home_folder_id
}, { }, {
"_id": { "_id": {
"$in": spaceIds [Op.in]: spaceIds
}, },
"creator": { "creator_id": {
"$ne": req.user._id [Op.ne]: req.user._id
} }
}] }]
}; };
Space db.Space
.find(q) .findAll({where: q, include: ['creator']})
.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 = db.spaceToObject(s);
return spaceObj; return spaceObj;
}); });
res.status(200).json(spaces); res.status(200).json(spaces);
...@@ -229,47 +217,43 @@ router.get('/', function(req, res, next) { ...@@ -229,47 +217,43 @@ router.get('/', function(req, res, next) {
} }
}); });
// create a space
router.post('/', function(req, res, next) { router.post('/', function(req, res, next) {
if (req.user) { if (req.user) {
var attrs = req.body; var attrs = req.body;
var createSpace = () => { var createSpace = () => {
attrs._id = uuidv4();
attrs.creator = req.user; attrs.creator_id = req.user._id;
attrs.edit_hash = crypto.randomBytes(64).toString('hex').substring(0, 7); attrs.edit_hash = crypto.randomBytes(64).toString('hex').substring(0, 7);
attrs.edit_slug = slug(attrs.name); attrs.edit_slug = slug(attrs.name);
var space = new Space(attrs); db.Space.create(attrs).then(createdSpace => {
space.save(function(err, createdSpace) { //if (err) res.sendStatus(400);
if (err) res.sendStatus(400); var membership = {
else { _id: uuidv4(),
var membership = new Membership({ user_id: req.user._id,
user: req.user, space_id: attrs._id,
space: createdSpace, role: "admin"
role: "admin" };
});
membership.save(function(err, createdTeam) { db.Membership.create(membership).then(() => {
if (err) { res.status(201).json(createdSpace);
res.status(400).json(err); });
} else {
res.status(201).json(createdSpace);
}
});
}
}); });
} }
if (attrs.parent_space_id) { if (attrs.parent_space_id) {
Space.findOne({ db.Space.findOne({ where: {
"_id": attrs.parent_space_id "_id": attrs.parent_space_id
}).populate('creator', userMapping).exec((err, parentSpace) => { }}).then(parentSpace => {
if (parentSpace) { if (parentSpace) {
Space.roleInSpace(parentSpace, req.user, (err, role) => { db.getUserRoleInSpace(parentSpace, req.user, (role) => {
if ((role == "editor") || (role == "admin")) { if ((role == "editor") || (role == "admin")) {
createSpace(); createSpace();
} else { } else {
res.status(403).json({ res.status(403).json({
"error": "not editor in parent Space" "error": "not editor in parent Space. role: "+role
}); });
} }
}); });
...@@ -292,6 +276,30 @@ router.get('/:id', function(req, res, next) { ...@@ -292,6 +276,30 @@ router.get('/:id', function(req, res, next) {
res.status(200).json(req.space); res.status(200).json(req.space);
}); });
router.get('/:id/path', (req, res) => {
// build up a breadcrumb trail (path)
var path = [];
var buildPath = (space) => {
if (space.parent_space_id) {
db.Space.findOne({ where: {
"_id": space.parent_space_id
}}).then(parentSpace => {
if (space._id == parentSpace._id) {
console.error("error: circular parent reference for space " + space._id);
res.send("error: circular reference");
} else {
path.push(parentSpace);
buildPath(parentSpace);
}
});
} else {
// reached the top
res.json(path.reverse());
}
}
buildPath(req.space);
});
router.put('/:id', function(req, res) { router.put('/:id', function(req, res) {
var space = req.space; var space = req.space;
var newAttr = req.body; var newAttr = req.body;
...@@ -308,24 +316,17 @@ router.put('/:id', function(req, res) { ...@@ -308,24 +316,17 @@ router.put('/:id', function(req, res) {
delete newAttr['editor_name']; delete newAttr['editor_name'];
delete newAttr['creator']; delete newAttr['creator'];
Space.findOneAndUpdate({ db.Space.update(newAttr, {where: {
"_id": space._id "_id": space._id
}, { }}).then(space => {
"$set": newAttr res.distributeUpdate("Space", space);
}, {
"new": true
}, function(err, space) {
if (err) res.status(400).json(err);
else {
res.distributeUpdate("Space", space);
}
}); });
}); });
router.post('/:id/background', function(req, res, next) { router.post('/:id/background', function(req, res, next) {
var space = req.space; var space = req.space;
var newDate = new Date(); var newDate = new Date();
var fileName = (req.query.filename || "upload.bin").replace(/[^a-zA-Z0-9\.]/g, ''); var fileName = (req.query.filename || "upload.jpg").replace(/[^a-zA-Z0-9\.]/g, '');
var localFilePath = "/tmp/" + fileName; var localFilePath = "/tmp/" + fileName;
var writeStream = fs.createWriteStream(localFilePath); var writeStream = fs.createWriteStream(localFilePath);
var stream = req.pipe(writeStream); var stream = req.pipe(writeStream);
...@@ -334,38 +335,26 @@ router.post('/:id/background', function(req, res, next) { ...@@ -334,38 +335,26 @@ router.post('/:id/background', function(req, res, next) {
uploader.uploadFile("s" + req.space._id + "/bg_" + newDate.getTime() + "_" + fileName, "image/jpeg", localFilePath, function(err, backgroundUrl) { uploader.uploadFile("s" + req.space._id + "/bg_" + newDate.getTime() + "_" + fileName, "image/jpeg", localFilePath, function(err, backgroundUrl) {
if (err) res.status(400).json(err); if (err) res.status(400).json(err);
else { else {
var adv = space.advanced; if (space.background_uri) {
var oldPath = url.parse(req.space.background_uri).pathname;
if (adv.background_uri) {
var oldPath = url.parse(req.space.thumbnail_url).pathname;
uploader.removeFile(oldPath, function(err) { uploader.removeFile(oldPath, function(err) {
console.log("removed old bg error:", err); console.error("removed old bg error:", err);
}); });
} }
adv.background_uri = backgroundUrl; db.Space.update({
background_uri: backgroundUrl
Space.findOneAndUpdate({
"_id": space._id
}, {
"$set": {
advanced: adv
}
}, { }, {
"new": true where: { "_id": space._id }
}, function(err, space) { }, function(rows) {
if (err) { fs.unlink(localFilePath, function(err) {
res.sendStatus(400); if (err) {
} else { console.error(err);
fs.unlink(localFilePath, function(err) { res.status(400).json(err);
if (err) { } else {
console.error(err); res.status(200).json(space);
res.status(400).json(err); }
} else { });
res.status(200).json(space);
}
});
}
}); });
} }
}); });
...@@ -390,10 +379,10 @@ router.post('/:id/duplicate', (req, res, next) => { ...@@ -390,10 +379,10 @@ router.post('/:id/duplicate', (req, res, next) => {
}).populate('creator', userMapping).exec((err, parentSpace) => { }).populate('creator', userMapping).exec((err, parentSpace) => {
if (!parentSpace) { if (!parentSpace) {
res.status(404).json({ res.status(404).json({
"error": "parent space not found for dupicate" "error": "parent space not found for duplicate"
}); });
} else { } else {
Space.roleInSpace(parentSpace, req.user, (err, role) => { db.getUserRoleInSpace(parentSpace, req.user, (role) => {
if (role == "admin" ||  role == "editor") { if (role == "admin" ||  role == "editor") {
handleDuplicateSpaceRequest(req, res, parentSpace); handleDuplicateSpaceRequest(req, res, parentSpace);
} else { } else {
...@@ -415,15 +404,12 @@ router.delete('/:id', function(req, res, next) { ...@@ -415,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 {
...@@ -449,6 +435,7 @@ router.post('/:id/artifacts-pdf', function(req, res, next) { ...@@ -449,6 +435,7 @@ router.post('/:id/artifacts-pdf', function(req, res, next) {
fs.mkdir(outputFolder, function(db) { fs.mkdir(outputFolder, function(db) {
var images = outputFolder + "/" + rawName + "-page-%03d.jpeg"; var images = outputFolder + "/" + rawName + "-page-%03d.jpeg";
// FIXME not portable
exec.execFile("gs", ["-sDEVICE=jpeg", "-dDownScaleFactor=4", "-dDOINTERPOLATE", "-dNOPAUSE", "-dJPEGQ=80", "-dBATCH", "-sOutputFile=" + images, "-r250", "-f", localFilePath], {}, function(error, stdout, stderr) { exec.execFile("gs", ["-sDEVICE=jpeg", "-dDownScaleFactor=4", "-dDOINTERPOLATE", "-dNOPAUSE", "-dJPEGQ=80", "-dBATCH", "-sOutputFile=" + images, "-r250", "-f", localFilePath], {}, function(error, stdout, stderr) {
if (error === null) { if (error === null) {
...@@ -532,6 +519,7 @@ router.post('/:id/artifacts-pdf', function(req, res, next) { ...@@ -532,6 +519,7 @@ router.post('/:id/artifacts-pdf', function(req, res, next) {
}, function(err, artifacts) { }, function(err, artifacts) {
// FIXME not portable
exec.execFile("rm", ["-r", outputFolder], function(err) { exec.execFile("rm", ["-r", outputFolder], function(err) {
res.status(201).json(_.flatten(artifacts)); res.status(201).json(_.flatten(artifacts));
...@@ -551,6 +539,7 @@ router.post('/:id/artifacts-pdf', function(req, res, next) { ...@@ -551,6 +539,7 @@ router.post('/:id/artifacts-pdf', function(req, res, next) {
}); });
} else { } else {
console.error("error:", error); console.error("error:", error);
// FIXME not portable
exec.execFile("rm", ["-r", outputFolder], function(err) { exec.execFile("rm", ["-r", outputFolder], function(err) {
fs.unlink(localFilePath); fs.unlink(localFilePath);
res.status(400).json({}); res.status(400).json({});
......
"use strict";
var config = require('config');
require('../../models/schema');
var redis = require('../../helpers/redis');
var mailer = require('../../helpers/mailer');
var fs = require('fs');
var _ = require('underscore');
var crypto = require('crypto');
var bcrypt = require('bcryptjs');
var express = require('express');
var router = express.Router();
var userMapping = { '_id': 1, 'nickname': 1, 'email': 1};
router.get('/:id', (req, res) => {
res.status(200).json(req.user.team);
});
router.put('/:id', (req, res) => {
var team = req.user.team;
if (!team) {
res.status(400).json({"error": "user in no team"});
} else {
var newAttr = req.body;
newAttr.updated_at = new Date();
delete newAttr['_id'];
if(newAttr['subdomain']) {
newAttr['subdomain'] = newAttr['subdomain'].toLowerCase();
}
const new_subdomain = newAttr['subdomain'];
var forbidden_subdomains = [];
function updateTeam() {
Team.findOneAndUpdate({"_id": team._id}, {"$set": newAttr}, {"new": true}, (err, team) => {
if (err) res.status(400).json(err);
else {
res.status(200).json(team);
}
});
}
var isForbidden = forbidden_subdomains.indexOf(new_subdomain) > -1;
if (isForbidden) {
res.bad_request("subdomain not valid");
} else {
if (new_subdomain) {
Team.findOne({"domain": new_subdomain}).exec((err, team) => {
if(team) {
res.bad_request("subdomain already used");
} else {
updateTeam()
}
});
} else {
updateTeam()
}
}
}
});
router.get('/:id/memberships', (req, res) => {
User
.find({team: req.user.team})
.populate("team")
.exec(function(err, users){
if (err) res.status(400).json(err);
else {
res.status(200).json(users);
}
});
});
router.post('/:id/memberships', (req, res, next) => {
if (req.body.email) {
const email = req.body.email.toLowerCase();
const team = req.user.team;
User.findOne({"email": email}).populate('team').exec((err, user) => {
if (user) {
const code = crypto.randomBytes(64).toString('hex').substring(0,7);
team.invitation_codes.push(code);
team.save((err) => {
if (err){ res.status(400).json(err); }
else {
mailer.sendMail(email, req.i18n.__("team_invite_membership_subject", team.name), req.i18n.__("team_invite_membership_body", team.name), { action: {
link: config.endpoint + "/teams/" + req.user.team._id + "/join?code=" + code,
name: req.i18n.__("team_invite_membership_action"),
teamname: team.name
}});
res.status(201).json(user);
}
});
} else {
// complete new user
const password = crypto.randomBytes(64).toString('hex').substring(0,7);
const confirmation_token = crypto.randomBytes(64).toString('hex').substring(0,7);
bcrypt.genSalt(10, (err, salt) => {
bcrypt.hash(password, salt, (err, hash) => {
crypto.randomBytes(16, (ex, buf) => {
const token = buf.toString('hex');
var u = new User({
email: email,
account_type: "email",
nickname: email,
team: team._id,
password_hash: hash,
payment_plan_key: team.payment_plan_key,
confirmation_token: confirmation_token,
preferences: {
language: req.i18n.locale
}
});
u.save((err) => {
if(err) res.sendStatus(400);
else {
var homeSpace = new Space({
name: req.i18n.__("home"),
space_type: "folder",
creator: u
});
homeSpace.save((err, homeSpace) => {
if (err) res.sendStatus(400);
else {
u.home_folder_id = homeSpace._id;
u.save((err) => {
User.find({"_id": {"$in": team.admins }}).exec((err, admins) => {
admins.forEach((admin) => {
var i18n = req.i18n;
if(admin.preferences && admin.preferences.language){
i18n.setLocale(admin.preferences.language || "en");
}
mailer.sendMail(admin.email, i18n.__("team_invite_membership_subject", team.name), i18n.__("team_invite_admin_body", email, team.name, password), { teamname: team.name });
});
});
mailer.sendMail(email, req.i18n.__("team_invite_membership_subject", team.name), req.i18n.__("team_invite_user_body", team.name, password), { action: {
link: config.endpoint + "/users/byteam/" + req.user.team._id + "/join?confirmation_token=" + confirmation_token,
name: req.i18n.__("team_invite_membership_action")
}, teamname: team.name });
if (err) res.status(400).json(err);
else{
res.status(201).json(u)
}
});
}
});
}
});
});
});
});
}
});
} else {
res.status(400).json({"error": "email missing"});
}
});
router.put('/:id/memberships/:user_id', (req, res) => {
User.findOne({_id: req.params.user_id}, (err,mem) => {
if (err) res.sendStatus(400);
else {
if(user.team._id == req.user.team._id){
user['team'] = req.user.team._id;
user.save((err) => {
res.sendStatus(204);
});
} else {
res.sendStatus(403);
}
}
});
});
router.get('/:id/memberships/:user_id/promote', (req, res) => {
User.findOne({_id: req.params.user_id}, (err,user) => {
if (err) res.sendStatus(400);
else {
if (user.team.toString() == req.user.team._id.toString()) {
var team = req.user.team;
var adminIndex = team.admins.indexOf(user._id);
if (adminIndex == -1) {
team.admins.push(user._id);
team.save((err, team) => {
res.status(204).json(team);
});
} else {
res.status(400).json({"error": "already admin"});
}
} else {
res.status(403).json({"error": "team id not correct"});
}
}
});
});
router.get('/:id/memberships/:user_id/demote', (req, res, next) => {
User.findOne({_id: req.params.user_id}, (err,user) => {
if (err) res.sendStatus(400);
else {
if (user.team.toString() == req.user.team._id.toString()) {
const team = req.user.team;
const adminIndex = team.admins.indexOf(user._id);
if(adminIndex > -1) {
team.admins.splice(adminIndex,1);
team.save((err, team) => {
res.status(204).json(team);
});
} else {
res.sendStatus(404);
}
} else {
res.sendStatus(403);
}
}
});
});
router.delete('/:id/memberships/:user_id', (req, res) => {
User.findOne({_id: req.params.user_id}).populate('team').exec((err,user) => {
if (err) res.sendStatus(400);
else {
const currentUserId = req.user._id.toString();
const team = req.user.team;
const isAdmin = (req.user.team.admins.filter( mem => {
return mem == currentUserId;
}).length == 1)
if (isAdmin) {
user.team = null;
user.payment_plan_key = "free";
user.save( err => {
const adminIndex = team.admins.indexOf(user._id);
if(adminIndex > -1) {
team.admins.splice(adminIndex,1);
team.save((err, team) => {
console.log("admin removed");
});
}
res.sendStatus(204);
});
} else {
res.status(403).json({"error": "not admin"});
}
}
});
});
module.exports = router;
"use strict"; "use strict";
var config = require('config'); var config = require('config');
require('../../models/schema'); const db = require('../../models/db');
const uuidv4 = require('uuid/v4');
var mailer = require('../../helpers/mailer'); var mailer = require('../../helpers/mailer');
var uploader = require('../../helpers/uploader'); var uploader = require('../../helpers/uploader');
var importer = require('../../helpers/importer'); var importer = require('../../helpers/importer');
var bcrypt = require('bcryptjs'); var bcrypt = require('bcryptjs');
var crypo = require('crypto'); var crypto = require('crypto');
var swig = require('swig'); var swig = require('swig');
var async = require('async'); var async = require('async');
var _ = require('underscore'); var _ = require('underscore');
...@@ -20,6 +21,7 @@ var URL = require('url').URL; ...@@ -20,6 +21,7 @@ var URL = require('url').URL;
var express = require('express'); var express = require('express');
var router = express.Router(); var router = express.Router();
var glob = require('glob');
router.get('/current', function(req, res, next) { router.get('/current', function(req, res, next) {
if (req.user) { if (req.user) {
...@@ -30,231 +32,95 @@ router.get('/current', function(req, res, next) { ...@@ -30,231 +32,95 @@ router.get('/current', function(req, res, next) {
} }
}); });
// create user
router.post('/', function(req, res) { router.post('/', function(req, res) {
if (req.body["email"] && req.body["password"]) { if (!req.body["email"] || !req.body["password"]) {
var email = req.body["email"].toLowerCase();
var nickname = req.body["nickname"];
var password = req.body["password"];
var password_confirmation = req.body["password_confirmation"];
if (password_confirmation == password) {
if (validator.isEmail(email)) {
var createUser = function() {
bcrypt.genSalt(10, function(err, salt) {
bcrypt.hash(password, salt, function(err, hash) {
crypo.randomBytes(16, function(ex, buf) {
var token = buf.toString('hex');
var u = new User({
email: email,
account_type: "email",
nickname: nickname,
password_hash: hash,
preferences: {
language: req.i18n.locale
},
confirmation_token: token
});
u.save(function (err) {
if (err) res.sendStatus(400);
else {
var homeSpace = new Space({
name: req.i18n.__("home"),
space_type: "folder",
creator: u
});
homeSpace.save((err, homeSpace) => {
if (err) res.sendStatus(400);
else {
u.home_folder_id = homeSpace._id;
u.save((err) => {
mailer.sendMail(u.email, req.i18n.__("confirm_subject"), req.i18n.__("confirm_body"), {
action: {
link: config.endpoint + "/confirm/" + u.confirmation_token,
name: req.i18n.__("confirm_action")
}
});
if (err) res.status(400).json(err);
else {
res.status(201).json({});
}
});
}
});
}
});
});
});
});
};
User.find({email: email}, (function (err, users) {
if (err) {
res.status(400).json({"error":"password_confirmation"});
} else {
if (users.length === 0) {
var domain = email.slice(email.lastIndexOf('@')+1);
Domain.findOne({domain: domain}, function(err, domain) {
if(domain){
if(domain.edu) {
createUser();
} else {
res.status(400).json({"error":"domain_blocked"});
}
} else {
createUser();
}
});
} else {
res.status(400).json({"error":"user_email_already_used"});
}
}
}));
} else {
res.status(400).json({"error":"email_invalid"});
}
} else {
res.status(400).json({"error":"password_confirmation"});
}
} else {
res.status(400).json({"error":"email or password missing"}); res.status(400).json({"error":"email or password missing"});
return;
} }
});
var email = req.body["email"].toLowerCase();
router.get('/oauth2callback/url', function(req, res) { var nickname = req.body["nickname"];
var google = require('googleapis'); var password = req.body["password"];
var OAuth2 = google.auth.OAuth2; var password_confirmation = req.body["password_confirmation"];
var oauth2Client = new OAuth2( if (password_confirmation != password) {
config.google_access, res.status(400).json({"error":"password_confirmation"});
config.google_secret, return;
config.endpoint + "/login" }
);
if (!validator.isEmail(email)) {
var url = oauth2Client.generateAuthUrl({ res.status(400).json({"error":"email_invalid"});
access_type: 'online', return;
scope: "email" }
});
var createUser = function() {
res.status(200).json({"url":url}); bcrypt.genSalt(10, function(err, salt) {
}); bcrypt.hash(password, salt, function(err, hash) {
crypto.randomBytes(16, function(ex, buf) {
router.get('/loginorsignupviagoogle', function(req, res) { var token = buf.toString('hex');
var google = require('googleapis');
var OAuth2 = google.auth.OAuth2; var u = {
var plus = google.plus('v1'); _id: uuidv4(),
email: email,
var oauth2Client = new OAuth2( account_type: "email",
config.google_access, nickname: nickname,
config.google_secret, password_hash: hash,
config.endpoint + "/login" prefs_language: req.i18n.locale,
); confirmation_token: token
};
var loginUser = function(user, cb) {
crypo.randomBytes(48, function(ex, buf) { db.User.create(u)
var token = buf.toString('hex'); .error(err => {
var session = { res.sendStatus(400);
token: token, })
created_at: new Date() .then(u => {
}; var homeSpace = {
if(!user.sessions) _id: uuidv4(),
user.sessions = []; name: req.i18n.__("home"),
user.sessions.push(session); space_type: "folder",
user.save(function(err, user) { creator_id: u._id
cb(session); };
}); db.Space.create(homeSpace)
}); .error(err => {
}; res.sendStatus(400);
})
var code = req.query.code; .then(homeSpace => {
oauth2Client.getToken(code, function(err, tokens) { u.home_folder_id = homeSpace._id;
u.save()
if (err) res.status(400).json(err); .then(() => {
else { res.status(201).json({});
var apiUrl = "https://www.googleapis.com/oauth2/v1/userinfo?alt=json&access_token=" + tokens.access_token;
mailer.sendMail(u.email, req.i18n.__("confirm_subject"), req.i18n.__("confirm_body"), {
var finalizeLogin = function(session){ action: {
res.cookie('sdsession', session.token, { httpOnly: true }); link: config.endpoint + "/confirm/" + u.confirmation_token,
res.status(201).json(session); name: req.i18n.__("confirm_action")
};
request.get(apiUrl, function(error, response, body) {
if (error) res.status(400).json(error);
else {
const data = JSON.parse(body);
const email = data.email;
const name = data.name;
User.findOne({email: email}, function (err, user) {
if (user) {
// login new google user
if (user.account_type == "google") {
// just login
loginUser(user, (session) => {
finalizeLogin(session);
});
} else {
res.status(400).json({"error":"user_email_already_used"});
}
} else {
const u = new User({
email: email,
account_type: "google",
nickname: name,
avatar_thumb_uri: body.picture,
preferences: {
language: req.i18n.locale
},
confirmed_at: new Date()
});
u.save(function (err) {
if (err) res.status(400).json(err);
else {
var homeSpace = new Space({
name: req.i18n.__("home"),
space_type: "folder",
creator: u
});
homeSpace.save(function(err, homeSpace) {
if (err) res.status(400).json(err);
else {
u.home_folder_id = homeSpace._id;
u.save(function(err){
if (err) res.sendStatus(400);
else {
mailer.sendMail(u.email, req.i18n.__("welcome_subject"), req.i18n.__("welcome_body"), {});
loginUser(u, function(session) {
finalizeLogin(session);
});
} }
}); });
} })
}); .error(err => {
} res.status(400).json(err);
}); });
} })
}); });
} });
}); });
} });
}); };
db.User.findAll({where: {email: email}})
.then(users => {
if (users.length == 0) {
//var domain = email.slice(email.lastIndexOf('@')+1);
createUser();
} else {
res.status(400).json({"error":"user_email_already_used"});
}
})
}); });
router.get('/ ', function(req, res, next) { router.get('/current', function(req, res, next) {
if (req.user) { if (req.user) {
console.log(req.user.team);
res.status(200).json(req.user); res.status(200).json(req.user);
} else { } else {
res.status(401).json({"error":"user_not_found"}); res.status(401).json({"error":"user_not_found"});
...@@ -262,19 +128,15 @@ router.get('/ ', function(req, res, next) { ...@@ -262,19 +128,15 @@ router.get('/ ', 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);
...@@ -292,12 +154,8 @@ router.post('/:id/password', function(req, res, next) { ...@@ -292,12 +154,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);
}
}); });
}); });
}); });
...@@ -326,7 +184,7 @@ router.delete('/:id', (req, res, next) => { ...@@ -326,7 +184,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);
}); });
} }
...@@ -370,19 +228,15 @@ router.post('/:user_id/avatar', (req, res, next) => { ...@@ -370,19 +228,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);
}
});
}
}); });
} }
}); });
...@@ -400,31 +254,20 @@ router.post('/feedback', function(req, res, next) { ...@@ -400,31 +254,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") {
crypo.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"});
}
} }
}); });
}); });
...@@ -433,29 +276,25 @@ router.post('/password_reset_requests/:confirm_token/confirm', function(req, res ...@@ -433,29 +276,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);
} }
}); });
}); });
...@@ -468,6 +307,12 @@ router.post('/:user_id/confirm', function(req, res, next) { ...@@ -468,6 +307,12 @@ router.post('/:user_id/confirm', function(req, res, next) {
res.sendStatus(201); res.sendStatus(201);
}); });
router.get('/:user_id/importables', function(req, res, next) {
glob('*.zip', function(err, files) {
res.status(200).json(files);
});
});
router.get('/:user_id/import', function(req, res, next) { router.get('/:user_id/import', function(req, res, next) {
if (req.query.zip) { if (req.query.zip) {
res.send("importing"); res.send("importing");
......
"use strict"; "use strict";
var config = require('config'); var config = require('config');
require('../../models/schema'); require('../../models/db');
var fs = require('fs'); var fs = require('fs');
var phantom = require('node-phantom-simple'); var phantom = require('node-phantom-simple');
......
"use strict"; "use strict";
const config = require('config'); const config = require('config');
require('../models/schema'); require('../models/db');
const redis = require('../helpers/redis'); const redis = require('../helpers/redis');
const express = require('express'); const express = require('express');
...@@ -9,7 +9,6 @@ const crypto = require('crypto'); ...@@ -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' });
...@@ -95,10 +94,6 @@ router.get('/logout', (req, res) => { ...@@ -95,10 +94,6 @@ router.get('/logout', (req, res) => {
res.render('spacedeck'); res.render('spacedeck');
}); });
router.get('/users/oauth2callback', (req, res) => {
res.render('spacedeck');
});
router.get('/contact', (req, res) => { router.get('/contact', (req, res) => {
res.render('public/contact'); res.render('public/contact');
}); });
...@@ -185,107 +180,6 @@ router.get('/spaces/:id', (req, res) => { ...@@ -185,107 +180,6 @@ router.get('/spaces/:id', (req, res) => {
} else res.render('spacedeck', { title: 'Space' }); } else res.render('spacedeck', { title: 'Space' });
}); });
router.get('/users/byteam/:team_id/join', (req, res) => {
if (!req.user) {
const q = {confirmation_token: req.query.confirmation_token, account_type: "email", team: req.params.team_id};
User.findOne(q, (err, user) => {
if (err) {
res.status(400).json({"error":"session.users"});
} else {
if (user) {
crypto.randomBytes(48, function(ex, buf) {
const token = buf.toString('hex');
var session = {
token: token,
ip: req.ip,
device: "web",
created_at: new Date()
};
if (!user.sessions)
user.sessions = [];
user.sessions.push(session);
user.confirmed_at = new Date();
user.confirmation_token = null;
user.save(function(err, result) {
// FIXME
const secure = process.env.NODE_ENV == "production" || process.env.NODE_ENV == "staging";
const domain = (process.env.NODE_ENV == "production") ? ".spacedeck.com" : ".spacedecklocal.de";
res.cookie('sdsession', token, { domain: domain, httpOnly: true, secure: secure});
res.redirect("/spaces");
});
});
} else {
res.status(404).json({"error": "not found"});
}
}
});
} else {
res.redirect("/spaces");
}
});
router.get('/teams/:id/join', function(req, res, next) {
if (req.user) {
if (!req.user.team) {
Team.findOne({"_id": req.params.id}, function(err, team) {
if (team) {
const idx = team.invitation_codes.indexOf(req.query.code);
if (idx >= 0) {
const u = req.user;
u.team = team;
if(!u.confirmed_at) {
u.confirmed_at = new Date();
}
u.payment_plan_key = team.payment_plan_key;
u.save(function(err) {
if (err) res.status(400).json(err);
else {
team.invitation_condes = team.invitation_codes.slice(idx);
team.save(function(err) {
team.invitation_codes = null;
var finish = function(team, users) {
User.find({"_id": {"$in": team.admins}}).exec((err, admins) => {
if(admins) {
admins.forEach((admin) => {
mailer.sendMail(
admin.email,
req.i18n.__("team_new_member_subject", team.name),
req.i18n.__("team_new_member_body", u.email, team.name)
);
});
}
});
}
User.find({team: team}, function(err, users) {
finish(team, users);
res.redirect("/spaces");
});
});
}
});
} else {
res.redirect("/spaces?error=team_code_notfound");
}
} else {
res.redirect("/spaces?error=team_notfound");
}
});
} else {
res.redirect("/spaces?error=team_already");
}
} else res.redirect("/login");
});
router.get('/qrcode/:id', function(req, res) { router.get('/qrcode/:id', function(req, res) {
Space.findOne({"_id": req.params.id}).exec(function(err, space) { Space.findOne({"_id": req.params.id}).exec(function(err, space) {
if (space) { if (space) {
......
"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);
}
});*/
<div id="team" class="dialog in" style="padding:100px;z-index:20000;position:absolute;width:100%;min-height:100%;background-color:#eee" v-if="active_view == 'account' && user" v-cloak> <div id="team" class="dialog in" style="padding:100px;z-index:20000;position:absolute;width:100%;min-height:100%;background-color:#eee" v-if="active_view == 'account' && user" v-cloak>
<a href="/spaces" class="btn btn-round btn-icon btn-stroke-darken btn-md pull-right" style="position:absolute;top:30px;right:30px"><span class="icon icon-cross-0"></span></a> <a href="/spaces" class="btn btn-round btn-icon btn-dark btn-md pull-right" style="position:absolute;top:30px;right:30px"><span class="icon icon-cross-0"></span></a>
<div class="dialog-tabs" style="margin:auto"> <div class="dialog-tabs" style="margin:auto">
<div class="dialog-tab" v-bind:class="{open:account=='profile'}" v-on:click="account='profile'"><span>[[__("profile_caption")]]</span></div> <div class="dialog-tab" v-bind:class="{open:account=='profile'}" v-on:click="account='profile'"><span>[[__("profile_caption")]]</span></div>
...@@ -80,21 +80,29 @@ ...@@ -80,21 +80,29 @@
<span class="icon icon-check"></span> <span>[[__("confirmation_sent_another")]]</span> <span class="icon icon-check"></span> <span>[[__("confirmation_sent_another")]]</span>
</p> </p>
</div> </div>
<div class="form-group">
<label class="label">Spacedeck.com Data Import</label>
<p v-if="!importables">No .ZIP files found in Spacedeck application folder.</p>
<ul>
<li v-for="f in importables">{{f}} <button v-on:click="start_zip_import(f)">Start Import</button></li>
</ul>
</div>
</div> </div>
</div> </div>
<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 +112,8 @@ ...@@ -104,8 +112,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>
......
<div id="space" class="section board active mouse-{{mouse_state}} tool-{{active_tool}}"> <div id="space" class="section board active mouse-{{mouse_state}} tool-{{active_tool}}">
<div class="space-bounds" style="width:{{active_space.advanced.width*bounds_zoom}}px;height:{{active_space.advanced.height*bounds_zoom}}px;"></div> <div class="space-bounds" style="width:{{active_space.width*bounds_zoom}}px;height:{{active_space.height*bounds_zoom}}px;"></div>
<div class="wrapper" <div class="wrapper"
style="transform:scale({{viewport_zoom}});transform-origin:0 0;width:{{active_space.advanced.width}}px;height:{{active_space.advanced.height}}px;background-image:url('{{active_space.advanced.background_uri}}');background-color:{{active_space.advanced.background_color}};margin-left:{{bounds_margin_horiz}}px;margin-top:{{bounds_margin_vert}}px" > style="transform:scale({{viewport_zoom}});transform-origin:0 0;width:{{active_space.width}}px;height:{{active_space.height}}px;background-image:url('{{active_space.background_uri}}');background-color:{{active_space.background_color}};margin-left:{{bounds_margin_horiz}}px;margin-top:{{bounds_margin_vert}}px" >
<div v-repeat="a : active_space_artifacts" <div v-repeat="a : active_space_artifacts"
v-class="text-editing:(editing_artifact_id==a._id && (a.view.major_type=='text' || a.view.major_type=='shape'))" v-class="text-editing:(editing_artifact_id==a._id && (a.view.major_type=='text' || a.view.major_type=='shape'))"
...@@ -81,7 +81,7 @@ ...@@ -81,7 +81,7 @@
</div> </div>
<div class="tl-controls"> <div class="tl-controls">
<div class="btn btn-md btn-toggle btn-round" v-class="alt:a.player_view.state=='playing'" v-show="a.board.w>=200 || a.player_view.state!='playing'"> <div class="btn btn-md btn-toggle btn-round" v-class="alt:a.player_view.state=='playing'" v-show="a.w>=200 || a.player_view.state!='playing'">
<span class="btn-option play"> <span class="btn-option play">
<span class="icon icon-controls-play"></span> <span class="icon icon-controls-play"></span>
</span> </span>
...@@ -95,8 +95,8 @@ ...@@ -95,8 +95,8 @@
<span class="icon icon-controls-stop"></span> <span class="icon icon-controls-stop"></span>
</span> </span>
<span class="tl-title" v-show="a.board.w>=250 && a.board.h>=150">{{a.view.filename}}</span> <span class="tl-title" v-show="a.w>=250 && a.h>=150">{{a.view.filename}}</span>
<span class="tl-times" class="btn-group" v-show="a.board.w>=400 && a.board.h>=150"> <span class="tl-times" class="btn-group" v-show="a.w>=400 && a.h>=150">
<span class="btn btn-md btn-transparent no-p set-inpoint">{{a.player_view.current_time_string}} /</span> <span class="btn btn-md btn-transparent no-p set-inpoint">{{a.player_view.current_time_string}} /</span>
<span class="btn btn-md btn-transparent no-p set-outpoint">{{a.player_view.total_time_string}}</span> <span class="btn btn-md btn-transparent no-p set-outpoint">{{a.player_view.total_time_string}}</span>
</span> </span>
......
...@@ -198,7 +198,7 @@ ...@@ -198,7 +198,7 @@
<div id="space" v-cloak <div id="space" v-cloak
v-if="active_view == 'space' && active_space_loaded" v-if="active_view == 'space' && active_space_loaded"
class="section board active mouse-{{mouse_state}} tool-{{active_tool}}" class="section board active mouse-{{mouse_state}} tool-{{active_tool}}"
v-bind:style="{'background-color': active_space.advanced.background_color}" v-bind:style="{'background-color': active_space.background_color}"
v-sd-droppable="handle_data_drop;active_space" v-sd-droppable="handle_data_drop;active_space"
v-sd-whiteboard v-sd-whiteboard
v-on:scroll="handle_scroll" v-on:scroll="handle_scroll"
...@@ -206,16 +206,16 @@ ...@@ -206,16 +206,16 @@
<div id="space-clipboard" style="position:fixed;top:0;left:0;z-index:0;opacity:0;background-color:white"><textarea v-model="selected_artifacts_json" cols="2" rows="2" id="clipboard-ta" class="mousetrap"></textarea></div> <div id="space-clipboard" style="position:fixed;top:0;left:0;z-index:0;opacity:0;background-color:white"><textarea v-model="selected_artifacts_json" cols="2" rows="2" id="clipboard-ta" class="mousetrap"></textarea></div>
<div class="space-bounds" v-bind:style="{width: active_space.advanced.width*bounds_zoom + 'px', height: active_space.advanced.height*bounds_zoom + 'px', 'background-color': active_space.advanced.background_color}"></div> <div class="space-bounds" v-bind:style="{width: active_space.width*bounds_zoom + 'px', height: active_space.height*bounds_zoom + 'px', 'background-color': active_space.background_color}"></div>
<div class="wrapper" <div class="wrapper"
v-bind:style="{ v-bind:style="{
transform: 'scale('+viewport_zoom+')', transform: 'scale('+viewport_zoom+')',
'transform-origin': '0 0', 'transform-origin': '0 0',
width: active_space.advanced.width + 'px', width: active_space.width + 'px',
height: active_space.advanced.height + 'px', height: active_space.height + 'px',
'background-image': (active_space.advanced.background_uri)?'url(' + active_space.advanced.background_uri + ')':'', 'background-image': (active_space.background_uri)?'url(' + active_space.background_uri + ')':'',
'background-color': ''+active_space.advanced.background_color, 'background-color': ''+active_space.background_color,
'margin-left': bounds_margin_horiz + 'px', 'margin-left': bounds_margin_horiz + 'px',
'margin-top': bounds_margin_vert + 'px'}" > 'margin-top': bounds_margin_vert + 'px'}" >
...@@ -331,7 +331,7 @@ ...@@ -331,7 +331,7 @@
<source v-bind:src="a.payload_uri" v-bind:type="a.mime" v-if="a.payload_uri"/> <source v-bind:src="a.payload_uri" v-bind:type="a.mime" v-if="a.payload_uri"/>
</audio> </audio>
<div class="timeline" v-show="a.board.h>=64 && a.board.w>=170" v-bind:style="{'background-image': 'url(' + a.payload_thumbnail_web_uri +')'}"> <div class="timeline" v-show="a.h>=64 && a.w>=170" v-bind:style="{'background-image': 'url(' + a.payload_thumbnail_web_uri +')'}">
<div class="tl-current-time" v-bind:style="{width: a.player_view.current_time_float*100 + '%'}"></div> <div class="tl-current-time" v-bind:style="{width: a.player_view.current_time_float*100 + '%'}"></div>
<div class="tl-inpoint" v-bind:style="{left: a.player_view.inpoint_float*100 + '%'}" v-if="a.player_view.inpoint_float>0.0"></div> <div class="tl-inpoint" v-bind:style="{left: a.player_view.inpoint_float*100 + '%'}" v-if="a.player_view.inpoint_float>0.0"></div>
<div class="tl-outpoint" v-bind:style="{left: a.player_view.outpoint_float*100 + '%'}"></div> <div class="tl-outpoint" v-bind:style="{left: a.player_view.outpoint_float*100 + '%'}"></div>
...@@ -352,13 +352,13 @@ ...@@ -352,13 +352,13 @@
<span class="icon icon-controls-stop"></span> <span class="icon icon-controls-stop"></span>
</span> </span>
<span class="tl-title" v-show="a.board.w>=400">{{a.view.filename}}</span> <span class="tl-title" v-show="a.w>=400">{{a.view.filename}}</span>
<span class="tl-times" class="btn-group"> <span class="tl-times" class="btn-group">
<span class="btn btn-md btn-transparent no-p">{{a.player_view.current_time_string}}</span> <span class="btn btn-md btn-transparent no-p">{{a.player_view.current_time_string}}</span>
<span class="btn btn-md btn-transparent no-p" v-show="a.board.w>=170"> / {{a.player_view.total_time_string}}</span> <span class="btn btn-md btn-transparent no-p" v-show="a.w>=170"> / {{a.player_view.total_time_string}}</span>
</span> </span>
<span v-show="logged_in && a.board.w>=310"> <span v-show="logged_in && a.w>=310">
<a class="btn btn-xs btn-round btn-icon set-inpoint" title="Set Inpoint at Playhead"> <a class="btn btn-xs btn-round btn-icon set-inpoint" title="Set Inpoint at Playhead">
<span class="icon icon-edge-left"></span> <span class="icon icon-edge-left"></span>
</a> </a>
...@@ -464,7 +464,7 @@ ...@@ -464,7 +464,7 @@
<div v-if="active_space_loaded" v-cloak> <div v-if="active_space_loaded" v-cloak>
<div id="minimap" <div id="minimap"
v-bind:style="{width: ''+(active_space.advanced.width/minimap_scale)+'px', height: ''+(active_space.advanced.height/minimap_scale)+'px', bottom: '66px', right: '20px'}" v-bind:style="{width: ''+(active_space.width/minimap_scale)+'px', height: ''+(active_space.height/minimap_scale)+'px', bottom: '66px', right: '20px'}"
v-if="active_space" v-if="active_space"
v-on:mousedown="handle_minimap_mousedown($event)" v-on:mousedown="handle_minimap_mousedown($event)"
v-on:touchstart="handle_minimap_mousedown($event)" v-on:touchstart="handle_minimap_mousedown($event)"
...@@ -473,7 +473,7 @@ ...@@ -473,7 +473,7 @@
v-on:mouseleave="handle_minimap_mouseup($event)" v-on:mouseleave="handle_minimap_mouseup($event)"
v-on:touchend="handle_minimap_mouseup($event)" v-on:touchend="handle_minimap_mouseup($event)"
v-on:mouseup="handle_minimap_mouseup($event)"> v-on:mouseup="handle_minimap_mouseup($event)">
<div v-for="a in active_space_artifacts" v-bind:style="{left: ''+(a.board.x/minimap_scale)+ 'px', top: ''+(a.board.y/minimap_scale) + 'px', width: ''+(a.board.w/minimap_scale)+ 'px', height: ''+(a.board.h/minimap_scale) + 'px'}"></div> <div v-for="a in active_space_artifacts" v-bind:style="{left: ''+(a.x/minimap_scale)+ 'px', top: ''+(a.y/minimap_scale) + 'px', width: ''+(a.w/minimap_scale)+ 'px', height: ''+(a.h/minimap_scale) + 'px'}"></div>
<div class="window" v-bind:style="{left: ''+(scroll_left/minimap_scale) + 'px', top: ''+(scroll_top/minimap_scale)+ 'px', width: ''+(window_width/minimap_scale)+ 'px', height: ''+(window_height/minimap_scale) + 'px'}"></div> <div class="window" v-bind:style="{left: ''+(scroll_left/minimap_scale) + 'px', top: ''+(scroll_top/minimap_scale)+ 'px', width: ''+(window_width/minimap_scale)+ 'px', height: ''+(window_height/minimap_scale) + 'px'}"></div>
</div> </div>
......
...@@ -79,14 +79,14 @@ ...@@ -79,14 +79,14 @@
</div--> </div-->
<div class="" v-show="background_mode=='image'" v-if="active_space"> <div class="" v-show="background_mode=='image'" v-if="active_space">
<div class="background-image" v-bind:style="{height: '233px', 'background-image':'url('+active_space.advanced.background_uri+')', 'margin': '6px', 'border-radius': '3px'}" v-if="active_space.advanced.background_uri && !space_background_uploading"> <div class="background-image" v-bind:style="{height: '233px', 'background-image':'url('+active_space.background_uri+')', 'margin': '6px', 'border-radius': '3px'}" v-if="active_space.background_uri && !space_background_uploading">
</div> </div>
<div class="progress state-processing" v-if="space_background_uploading"> <div class="progress state-processing" v-if="space_background_uploading">
<div class="spinner"></div> <div class="spinner"></div>
</div> </div>
<div class="dialog-section no-b adapt" v-if="!active_space.advanced.background_uri && !space_background_uploading" v-on:touchstart="handle_touch_select_background_image()"> <div class="dialog-section no-b adapt" v-if="!active_space.background_uri && !space_background_uploading" v-on:touchstart="handle_touch_select_background_image()">
<label class="btn btn-xxl btn-transparent btn-icon"> <label class="btn btn-xxl btn-transparent btn-icon">
<span class="icon icon-picture-upload"></span> <span class="icon icon-picture-upload"></span>
<input id="background-uploader" type="file" accept="image/*" v-on:change="handle_section_background_upload($event)"> <input id="background-uploader" type="file" accept="image/*" v-on:change="handle_section_background_upload($event)">
...@@ -94,9 +94,9 @@ ...@@ -94,9 +94,9 @@
<p>[[__("upload_background_caption")]]</p> <p>[[__("upload_background_caption")]]</p>
</div> </div>
<div class="dialog-section no-p no-flex" v-if="active_space.advanced.background_uri"> <div class="dialog-section no-p no-flex" v-if="active_space.background_uri">
<div class="btn-cluster"> <div class="btn-cluster">
<label class="btn btn-transparent btn-block text-center" v-if="active_space.advanced.background_uri" v-on:touchstart="handle_touch_select_background_image()"> <label class="btn btn-transparent btn-block text-center" v-if="active_space.background_uri" v-on:touchstart="handle_touch_select_background_image()">
<input id="background-uploader" type="file" accept="image/*" v-on:chang="handle_section_background_upload($event)"> <input id="background-uploader" type="file" accept="image/*" v-on:chang="handle_section_background_upload($event)">
<span class="icon icon-picture-upload"></span> <span class="icon icon-picture-upload"></span>
<!-- Upload --> <!-- Upload -->
......
<div id="pick-mobile" v-if="active_space" class="dialog-section" v-show="opened_dialog=='mobile'"> <div id="pick-mobile" v-if="active_space" class="dialog-section" v-show="opened_dialog=='mobile'">
<h4 class="dialog-title">Mobile Upload</h4> <h4 class="dialog-title">Mobile Upload</h4>
<img v-if="active_space.edit_hash" v-bind:src="'/api/helper/qrcode/'+ active_space._id" /> <!--img v-if="active_space.edit_hash" v-bind:src="'/api/helper/qrcode/'+ active_space._id"-->
<p class="text-center"> <p class="text-center">
Install the Spacedeck App on your phone and scan this QR code to upload photos, sound, video or text. Install the Spacedeck App on your phone and scan this QR code to upload photos, sound, video or text.
......
...@@ -76,20 +76,7 @@ ...@@ -76,20 +76,7 @@
{% include "./zones.html" %} {% include "./zones.html" %}
</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'}">
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment