further porting, cleanups, artifact upload/conversion and space screenshots

This commit is contained in:
Lukas F. Hartmann 2018-04-11 21:14:00 +02:00
parent c549fcf9ec
commit 8dc48a84ba
14 changed files with 164 additions and 355 deletions

View File

@ -27,12 +27,11 @@ Spacedeck uses the following major building blocks:
- Vue.js: Frontend UI Framework (included)
- SQLite (included)
It also has some binary dependencies for media conversion and PDF export:
It also has some optional binary dependencies for advanced media conversion:
- graphicsmagick (for image conversion)
- ffmpeg (for video/audio conversion)
- ffmpeg and ffprobe (for video/audio conversion)
- audiowaveform (for audio waveform rendering) (https://github.com/bbcrd/audiowaveform)
- phantomjs (for website screenshot rendering and PDF export, included)
- ghostscript (gs, for PDF import)
By default, media files are uploaded to the ```storage``` folder.

12
app.js
View File

@ -79,8 +79,6 @@ app.use(helmet.hsts({
app.disable('x-powered-by');
app.use(helmet.noSniff())
// CUSTOM MIDDLEWARES
//app.use(require("./middlewares/error_helpers"));
app.use(require("./middlewares/session"));
//app.use(require("./middlewares/cors"));
@ -89,19 +87,17 @@ 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"));
// REAL ROUTES
app.use('/api/users', require('./routes/api/users'));
//app.use('/api/memberships', require('./routes/api/memberships'));
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/messages', require('./routes/api/space_messages'));
spaceRouter.use('/:id/digest', require('./routes/api/space_digest'));
//spaceRouter.use('/:id', require('./routes/api/space_exports'));
spaceRouter.use('/:id', require('./routes/api/space_exports'));
app.use('/api/sessions', require('./routes/api/sessions'));
//app.use('/api/webgrabber', require('./routes/api/webgrabber'));
@ -125,8 +121,6 @@ if (app.get('env') == 'development') {
module.exports = app;
// CONNECT TO DATABASE
//const mongoHost = process.env.MONGO_PORT_27017_TCP_ADDR || config.get('mongodb_host');
//mongoose.connect('mongodb://' + mongoHost + '/spacedeck');
db.init();
// START WEBSERVER

View File

@ -13,47 +13,9 @@ const db = require('../models/db');
const Sequelize = require('sequelize');
const Op = Sequelize.Op;
const fileExtensionMap = {
".amr" : "audio/AMR",
".ogg" : "audio/ogg",
".aac" : "audio/aac",
".mp3" : "audio/mpeg",
".mpg" : "video/mpeg",
".3ga" : "audio/3ga",
".mp4" : "video/mp4",
".wav" : "audio/wav",
".mov" : "video/quicktime",
".doc" : "application/msword",
".dot" : "application/msword",
".docx" : "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
".dotx" : "application/vnd.openxmlformats-officedocument.wordprocessingml.template",
".docm" : "application/vnd.ms-word.document.macroEnabled.12",
".dotm" : "application/vnd.ms-word.template.macroEnabled.12",
".xls" : "application/vnd.ms-excel",
".xlt" : "application/vnd.ms-excel",
".xla" : "application/vnd.ms-excel",
".xlsx" : "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
".xltx" : "application/vnd.openxmlformats-officedocument.spreadsheetml.template",
".xlsm" : "application/vnd.ms-excel.sheet.macroEnabled.12",
".xltm" : "application/vnd.ms-excel.template.macroEnabled.12",
".xlam" : "application/vnd.ms-excel.addin.macroEnabled.12",
".xlsb" : "application/vnd.ms-excel.sheet.binary.macroEnabled.12",
".ppt" : "application/vnd.ms-powerpoint",
".pot" : "application/vnd.ms-powerpoint",
".pps" : "application/vnd.ms-powerpoint",
".ppa" : "application/vnd.ms-powerpoint",
".pptx" : "application/vnd.openxmlformats-officedocument.presentationml.presentation",
".potx" : "application/vnd.openxmlformats-officedocument.presentationml.template",
".ppsx" : "application/vnd.openxmlformats-officedocument.presentationml.slideshow",
".ppam" : "application/vnd.ms-powerpoint.addin.macroEnabled.12",
".pptm" : "application/vnd.ms-powerpoint.presentation.macroEnabled.12",
".potm" : "application/vnd.ms-powerpoint.template.macroEnabled.12",
".ppsm" : "application/vnd.ms-powerpoint.slideshow.macroEnabled.12",
".key" : "application/x-iwork-keynote-sffkey",
".pages" : "application/x-iwork-pages-sffpages",
".numbers" : "application/x-iwork-numbers-sffnumbers",
".ttf" : "application/x-font-ttf"
};
const mime = require('mime-types');
const fileType = require('file-type');
const readChunk = require('read-chunk');
const convertableImageTypes = [
"image/png",
@ -73,7 +35,7 @@ const convertableVideoTypes = [
const convertableAudioTypes = [
"application/ogg",
"audio/AMR",
"audio/amr",
"audio/3ga",
"audio/wav",
"audio/3gpp",
@ -132,7 +94,7 @@ function createWaveform(fileName, localFilePath, callback){
function convertVideo(fileName, filePath, codec, callback, progress_callback) {
var ext = path.extname(fileName);
var presetMime = fileExtensionMap[ext];
var presetMime = mime.lookup(fileName);
var newExt = codec == "mp4" ? "mp4" : "ogv";
var convertedPath = filePath + "." + newExt;
@ -190,7 +152,7 @@ function convertVideo(fileName, filePath, codec, callback, progress_callback) {
function convertAudio(fileName, filePath, codec, callback) {
var ext = path.extname(fileName);
var presetMime = fileExtensionMap[ext];
var presetMime = mime.lookup(fileName);
var newExt = codec == "mp3" ? "mp3" : "ogg";
var convertedPath = filePath + "." + newExt;
@ -227,22 +189,14 @@ function createThumbnailForVideo(fileName, filePath, callback) {
function getMime(fileName, filePath, callback) {
var ext = path.extname(fileName);
var presetMime = fileExtensionMap[ext];
var presetMime = mime.lookup(fileName);
if (presetMime) {
callback(null, presetMime);
} else {
exec.execFile("file", ["-b","--mime-type", filePath], {}, function(error, stdout, stderr) {
console.log("file stdout: ",stdout);
if (stderr === '' && error == null) {
//filter special chars from commandline
var mime = stdout.replace(/[^a-zA-Z0-9\/\-]/g,'');
callback(null, mime);
} else {
console.log("getMime file error: ", error);
callback(error, null);
}
});
const buffer = readChunk.sync(filePath, 0, 4100);
var mimeType = fileType(buffer);
callback(null, mimeType);
}
}
@ -276,7 +230,7 @@ function resizeAndUpload(a, size, max, fileName, localFilePath, callback) {
}
var resizeAndUploadImage = function(a, mime, size, fileName, fileNameOrg, imageFilePath, originalFilePath, payloadCallback) {
var resizeAndUploadImage = function(a, mimeType, size, fileName, fileNameOrg, imageFilePath, originalFilePath, payloadCallback) {
async.parallel({
small: function(callback){
resizeAndUpload(a, size, 320, fileName, imageFilePath, callback);
@ -289,13 +243,13 @@ var resizeAndUploadImage = function(a, mime, size, fileName, fileNameOrg, imageF
},
original: function(callback){
var s3Key = "s"+ a.space_id.toString() + "/a" + a._id + "/" + fileNameOrg;
uploader.uploadFile(s3Key, mime, originalFilePath, function(err, url){
uploader.uploadFile(s3Key, mimeType, originalFilePath, function(err, url){
callback(null, url);
});
}
}, function(err, results) {
a.state = "idle";
a.mime = mime;
a.mime = mimeType;
var stats = fs.statSync(originalFilePath);
a.payload_size = stats["size"];
@ -325,21 +279,21 @@ var resizeAndUploadImage = function(a, mime, size, fileName, fileNameOrg, imageF
module.exports = {
convert: function(a, fileName, localFilePath, payloadCallback, progress_callback) {
getMime(fileName, localFilePath, function(err, mime){
console.log("[convert] fn: "+fileName+" local: "+localFilePath+" mime:", mime);
getMime(fileName, localFilePath, function(err, mimeType){
console.log("[convert] fn: "+fileName+" local: "+localFilePath+" mimeType:", mimeType);
if (!err) {
if (convertableImageTypes.indexOf(mime) > -1) {
if (convertableImageTypes.indexOf(mimeType) > -1) {
gm(localFilePath).size(function (err, size) {
console.log("[convert] gm:", err, size);
if (!err) {
if(mime == "application/pdf") {
if(mimeType == "application/pdf") {
var firstImagePath = localFilePath + ".jpeg";
exec.execFile("gs", ["-sDEVICE=jpeg","-dNOPAUSE", "-dJPEGQ=80", "-dBATCH", "-dFirstPage=1", "-dLastPage=1", "-sOutputFile=" + firstImagePath, "-r90", "-f", localFilePath], {}, function(error, stdout, stderr) {
if(error === null) {
resizeAndUploadImage(a, mime, size, fileName + ".jpeg", fileName, firstImagePath, localFilePath, function(err, a) {
resizeAndUploadImage(a, mimeType, size, fileName + ".jpeg", fileName, firstImagePath, localFilePath, function(err, a) {
fs.unlink(firstImagePath, function (err) {
payloadCallback(err, a);
});
@ -349,7 +303,7 @@ module.exports = {
}
});
} else if(mime == "image/gif") {
} else if(mimeType == "image/gif") {
//gifs are buggy after convertion, so we should not convert them
var s3Key = "s"+ a.space_id.toString() + "/a" + a._id.toString() + "/" + fileName;
@ -361,7 +315,7 @@ module.exports = {
var stats = fs.statSync(localFilePath);
a.state = "idle";
a.mime = mime;
a.mime = mimeType;
a.payload_size = stats["size"];
a.payload_thumbnail_web_uri = url;
@ -389,12 +343,12 @@ module.exports = {
});
} else {
resizeAndUploadImage(a, mime, size, fileName, fileName, localFilePath, localFilePath, payloadCallback);
resizeAndUploadImage(a, mimeType, size, fileName, fileName, localFilePath, localFilePath, payloadCallback);
}
} else payloadCallback(err);
});
} else if (convertableVideoTypes.indexOf(mime) > -1) {
} else if (convertableVideoTypes.indexOf(mimeType) > -1) {
async.parallel({
thumbnail: function(callback) {
createThumbnailForVideo(fileName, localFilePath, function(err, created){
@ -410,7 +364,7 @@ module.exports = {
});
},
ogg: function(callback) {
if (mime == "video/ogg") {
if (mimeType == "video/ogg") {
callback(null, "org");
} else {
convertVideo(fileName, localFilePath, "ogg", function(err, file) {
@ -426,7 +380,7 @@ module.exports = {
}
},
mp4: function(callback) {
if (mime == "video/mp4") {
if (mimeType == "video/mp4") {
callback(null, "org");
} else {
convertVideo(fileName, localFilePath, "mp4", function(err, file) {
@ -442,7 +396,7 @@ module.exports = {
}
},
original: function(callback){
uploader.uploadFile(fileName, mime, localFilePath, function(err, url){
uploader.uploadFile(fileName, mimeType, localFilePath, function(err, url){
callback(null, url);
});
}
@ -452,7 +406,7 @@ module.exports = {
if (err) payloadCallback(err, a);
else {
a.state = "idle";
a.mime = mime;
a.mime = mimeType;
var stats = fs.statSync(localFilePath);
a.payload_size = stats["size"];
@ -461,7 +415,7 @@ module.exports = {
a.payload_thumbnail_big_uri = results.thumbnail;
a.payload_uri = results.original;
if (mime == "video/mp4") {
if (mimeType == "video/mp4") {
a.payload_alternatives = [
{
mime: "video/ogg",
@ -497,7 +451,7 @@ module.exports = {
}
});
} else if (convertableAudioTypes.indexOf(mime) > -1) {
} else if (convertableAudioTypes.indexOf(mimeType) > -1) {
async.parallel({
ogg: function(callback) {
@ -535,7 +489,7 @@ module.exports = {
},
original: function(callback) {
var keyName = "s" + a.space_id.toString() + "/a" + a._id.toString() + "/" + fileName;
uploader.uploadFile(keyName, mime, localFilePath, function(err, url){
uploader.uploadFile(keyName, mimeType, localFilePath, function(err, url){
callback(null, url);
});
}
@ -546,7 +500,7 @@ module.exports = {
else {
a.state = "idle";
a.mime = mime;
a.mime = mimeType;
var stats = fs.statSync(localFilePath);
a.payload_size = stats["size"];
@ -579,12 +533,12 @@ module.exports = {
} else {
console.log("mime not matched for conversion, storing file");
console.log("mimeType not matched for conversion, storing file");
var keyName = "s" + a.space_id.toString() + "/a" + a._id.toString() + "/" + fileName;
uploader.uploadFile(keyName, mime, localFilePath, function(err, url) {
uploader.uploadFile(keyName, mimeType, localFilePath, function(err, url) {
a.state = "idle";
a.mime = mime;
a.mime = mimeType;
var stats = fs.statSync(localFilePath);
a.payload_size = stats["size"];
a.payload_uri = url;

View File

@ -1,8 +1,9 @@
'use strict';
require('../models/db');
var config = require('config');
var phantom = require('node-phantom-simple');
const db = require('../models/db');
const config = require('config');
const phantom = require('node-phantom-simple');
const os = require('os');
module.exports = {
// type = "pdf" or "png"
@ -10,7 +11,7 @@ module.exports = {
var spaceId = space._id;
var space_url = config.get("endpoint")+"/api/spaces/"+spaceId+"/html";
var export_path = "/tmp/"+spaceId+"."+type;
var export_path = os.tmpdir()+"/"+spaceId+"."+type;
var timeout = 5000;
if (type=="pdf") timeout = 30000;
@ -24,7 +25,7 @@ module.exports = {
var on_exit = function(exit_code) {
if (exit_code>0) {
console.log("phantom abnormal exit for url "+space_url);
console.error("phantom abnormal exit for url "+space_url);
if (!on_success_called && on_error) {
on_error();
}
@ -32,16 +33,16 @@ module.exports = {
};
phantom.create({ path: require('phantomjs-prebuilt').path }, function (err, browser) {
if(err){
console.log(err);
}else{
if (err) {
console.error(err);
} else {
return browser.createPage(function (err, page) {
console.log("page created, opening ",space_url);
if (type=="pdf") {
var psz = {
width: space.advanced.width+"px",
height: space.advanced.height+"px"
width: space.width+"px",
height: space.height+"px"
};
page.set('paperSize', psz);
}

View File

@ -97,7 +97,7 @@ module.exports = {
updated_at: {type: Sequelize.DATE, defaultValue: Sequelize.NOW}
}),
Artifact: sequelize.define('message', {
Message: sequelize.define('message', {
_id: {type: Sequelize.STRING, primaryKey: true},
space_id: Sequelize.STRING,
user_id: Sequelize.STRING,

View File

@ -21,9 +21,11 @@
"cookie-parser": "~1.4.3",
"csurf": "1.9.0",
"debug": "~2.6.3",
"electron": "^1.8.4",
"execSync": "latest",
"express": "~4.13.0",
"extract-zip": "^1.6.6",
"file-type": "^7.6.0",
"glob": "7.1.1",
"gm": "1.23.0",
"googleapis": "18.0.0",
@ -40,6 +42,7 @@
"lodash": "^4.3.0",
"log-timestamp": "latest",
"md5": "2.2.1",
"mime-types": "^2.1.18",
"mock-aws-s3": "^2.6.0",
"moment": "^2.19.3",
"mongoose": "4.9.3",
@ -51,6 +54,7 @@
"pm2": "latest",
"qr-image": "3.2.0",
"raven": "1.2.0",
"read-chunk": "^2.1.0",
"request": "2.81.0",
"sanitize-html": "^1.11.1",
"sequelize": "^4.37.6",

View File

@ -636,17 +636,14 @@ var SpacedeckSpaces = {
download_space: function() {
smoke.quiz(__("download_space"), function(e, test) {
if (e == "PDF"){
if (e == "PDF") {
this.download_space_as_pdf(this.active_space);
}else if (e == "ZIP"){
} else if (e == "ZIP") {
this.download_space_as_zip(this.active_space);
}else if (e == "TXT"){
this.download_space_as_list(this.active_space);
}
}.bind(this), {
button_1: "PDF",
button_2: "ZIP",
button_3: "TXT",
button_cancel:__("cancel")
});

View File

@ -1,6 +1,8 @@
"use strict";
var config = require('config');
const os = require('os');
const db = require('../../models/db');
const Sequelize = require('sequelize');
const Op = Sequelize.Op;
@ -124,8 +126,7 @@ router.post('/:artifact_id/payload', function(req, res, next) {
var fileName = (req.query.filename || "upload.bin").replace(/[^a-zA-Z0-9_\-\.]/g, '');
// FIXME TODO use portable tmp dir
var localFilePath = "/tmp/" + fileName;
var localFilePath = os.tmpdir() + "/" + fileName;
var writeStream = fs.createWriteStream(localFilePath);
var stream = req.pipe(writeStream);

View File

@ -1,6 +1,6 @@
"use strict";
var config = require('config');
require('../../models/db');
const db = require('../../models/db');
var redis = require('../../helpers/redis');
var mailer = require('../../helpers/mailer');
@ -49,26 +49,18 @@ var roleMapping = {
router.get('/png', function(req, res, next) {
var triggered = new Date();
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) {
Space.update({
"_id": req.space._id
}, {
"$set": {
thumbnail_updated_at: triggered
}
}, function(a, b, c) {});
db.Space.update({ thumbnail_updated_at: triggered }, {where : {"_id": req.space._id }});
phantom.takeScreenshot(req.space, "png",
function(local_path) {
var localResizedFilePath = local_path + ".thumb.jpg";
gm(local_path).resize(640, 480).quality(70.0).autoOrient().write(localResizedFilePath, function(err) {
if (err) {
console.error("screenshot resize error: ", err);
console.error("[space screenshot] resize error: ", err);
res.status(500).send("Error taking screenshot.");
return;
}
@ -76,22 +68,15 @@ router.get('/png', function(req, res, next) {
uploader.uploadFile(s3_filename, "image/jpeg", localResizedFilePath, function(err, thumbnailUrl) {
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.");
return;
}
var oldUrl = req.space.thumbnail_url;
Space.update({
"_id": req.space._id
}, {
"$set": {
thumbnail_url: thumbnailUrl
}
}, function(a, b, c) {
db.Space.update({ thumbnail_url: thumbnailUrl }, {where : {"_id": req.space._id }}).then(() => {
res.redirect(thumbnailUrl);
try {
if (oldUrl) {
var oldPath = url.parse(oldUrl).pathname;
@ -125,77 +110,6 @@ function make_export_filename(space, 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) {
var s3_filename = make_export_filename(req.space, "pdf");
@ -329,9 +243,10 @@ router.get('/zip', function(req, res, next) {
});
router.get('/html', function(req, res) {
Artifact.find({
console.log("!!!!! hello ");
db.Artifact.findAll({where: {
space_id: req.space._id
}, function(err, artifacts) {
}}).then(function(artifacts) {
var space = req.space;
res.send(space_render.render_space_as_html(space, artifacts));
});

View File

@ -3,6 +3,7 @@ var config = require('config');
const db = require('../../models/db');
const Sequelize = require('sequelize');
const Op = Sequelize.Op;
const uuidv4 = require('uuid/v4');
var redis = require('../../helpers/redis');
var mailer = require('../../helpers/mailer');
@ -55,13 +56,15 @@ router.post('/', function(req, res, next) {
var attrs = req.body;
attrs['space'] = req.space._id;
attrs['state'] = "pending";
var membership = new Membership(attrs);
attrs._id = uuidv4();
var membership = attrs;
var msg = attrs.personal_message;
if (membership.email_invited != req.user.email) {
User.findOne({
db.User.findOne({where:{
"email": membership.email_invited
}, function(err, user) {
}}, function(user) {
if (user) {
membership.user = user;
@ -70,35 +73,32 @@ router.post('/', function(req, res, next) {
membership.code = crypto.randomBytes(64).toString('hex').substring(0, 12);
}
membership.save(function(err) {
if (err) res.sendStatus(400);
else {
var accept_link = config.endpoint + "/accept/" + membership._id + "?code=" + membership.code;
db.Membership.create(membership).then(function() {
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;
}
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);
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);
});
});

View File

@ -1,6 +1,9 @@
"use strict";
var config = require('config');
require('../../models/db');
const db = require('../../models/db');
const Sequelize = require('sequelize');
const Op = Sequelize.Op;
const uuidv4 = require('uuid/v4');
var redis = require('../../helpers/redis');
var mailer = require('../../helpers/mailer');
@ -17,9 +20,7 @@ var request = require('request');
var url = require("url");
var path = require("path");
var crypto = require('crypto');
var qr = require('qr-image');
var glob = require('glob');
var gm = require('gm');
var express = require('express');
var router = express.Router({mergeParams: true});
@ -49,16 +50,18 @@ var roleMapping = {
// MESSAGES
router.get('/', function(req, res, next) {
Message.find({
space: req.space._id
}).populate('user', userMapping).exec(function(err, messages) {
res.status(200).json(messages);
});
db.Message.findAll({where:{
space_id: req.space._id
}})
//.populate('user', userMapping)
.then(function(messages) {
res.status(200).json(messages);
});
});
router.post('/', function(req, res, next) {
var attrs = req.body;
attrs.space = req.space;
attrs.space_id = req.space._id;
if (req.user) {
attrs.user = req.user;
@ -66,65 +69,24 @@ router.post('/', function(req, res, next) {
attrs.user = null;
}
var msg = new Message(attrs);
msg.save(function(err) {
if (err) res.status(400).json(erra);
else {
if (msg.message.length <= 1) return;
var msg = attrs;
msg._id = uuidv4();
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, function() {
if (msg.message.length <= 1) return;
// TODO reimplement notifications
res.distributeCreate("Message", msg);
});
});
router.delete('/:message_id', function(req, res, next) {
Message.findOne({
db.Message.findOne({where:{
"_id": req.params.message_id
}, function(err, msg) {
}}, function(msg) {
if (!msg) {
res.sendStatus(404);
} else {
msg.remove(function(err) {
msg.destroy(function(err) {
if (err) res.status(400).json(err);
else {
if (msg) {

View File

@ -114,32 +114,33 @@ router.get('/', function(req, res, next) {
});
} else if (req.query.search) {
Membership.find({
db.Membership.findAll({where:{
user: req.user._id
}, function(err, memberships) {
}}).then(memberships => {
var validMemberships = memberships.filter(function(m) {
if (!m.space || (m.space == "undefined"))
if (!m.space_id || (m.space_id == "undefined"))
return false;
else
return mongoose.Types.ObjectId.isValid(m.space.toString());
return true;
});
var spaceIds = validMemberships.map(function(m) {
return new mongoose.Types.ObjectId(m.space);
return m.space_id;
});
var q = {
"$or": [{"creator": req.user._id},
// TODO FIXME port
var q = { where: {
"$or": [{"creator_id": req.user._id},
{"_id": {"$in": spaceIds}},
{"parent_space_id": {"$in": spaceIds}}],
name: new RegExp(req.query.search, "i")
name: new RegExp(req.query.search, "i")}
};
Space
.find(q)
.populate('creator', userMapping)
.exec(function(err, spaces) {
db.Space
.findAll(q)
//.populate('creator', userMapping)
.then(function(spaces) {
if (err) console.error(err);
var updatedSpaces = spaces.map(function(s) {
var spaceObj = s.toObject();
@ -151,15 +152,14 @@ router.get('/', function(req, res, next) {
} else if (req.query.parent_space_id && req.query.parent_space_id != req.user.home_folder_id) {
Space
.findOne({
db.Space
.findOne({where: {
_id: req.query.parent_space_id
})
.populate('creator', userMapping)
.exec(function(err, space) {
}})
//.populate('creator', userMapping)
.then(function(space) {
if (space) {
db.getUserRoleInSpace(space, req.user, function(role) {
if (role == "none") {
if(space.access_mode == "public") {
role = "viewer";
@ -167,12 +167,12 @@ router.get('/', function(req, res, next) {
}
if (role != "none") {
Space
.find({
db.Space
.findAll({where:{
parent_space_id: req.query.parent_space_id
})
.populate('creator', userMapping)
.exec(function(err, spaces) {
}})
//.populate('creator', userMapping)
.then(function(spaces) {
res.status(200).json(spaces);
});
} else {
@ -185,9 +185,6 @@ router.get('/', function(req, res, next) {
});
} else {
console.log("!!!!!!!!!! spaces lookup");
db.Membership.findAll({ where: {
user_id: req.user._id
}}).then(memberships => {
@ -243,9 +240,6 @@ router.post('/', function(req, res, next) {
db.Space.create(attrs).then(createdSpace => {
//if (err) res.sendStatus(400);
console.log("!!!!!!!!!! createdSpace:",createdSpace);
var membership = {
_id: uuidv4(),
user_id: req.user._id,
@ -342,7 +336,7 @@ router.put('/:id', function(req, res) {
router.post('/:id/background', function(req, res, next) {
var space = req.space;
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 writeStream = fs.createWriteStream(localFilePath);
var stream = req.pipe(writeStream);
@ -351,38 +345,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) {
if (err) res.status(400).json(err);
else {
var adv = space.advanced;
if (adv.background_uri) {
var oldPath = url.parse(req.space.thumbnail_url).pathname;
if (space.background_uri) {
var oldPath = url.parse(req.space.background_uri).pathname;
uploader.removeFile(oldPath, function(err) {
console.error("removed old bg error:", err);
});
}
adv.background_uri = backgroundUrl;
Space.findOneAndUpdate({
"_id": space._id
db.Space.update({
background_uri: backgroundUrl
}, {
"$set": {
advanced: adv
}
}, {
"new": true
}, function(err, space) {
if (err) {
res.sendStatus(400);
} else {
fs.unlink(localFilePath, function(err) {
if (err) {
console.error(err);
res.status(400).json(err);
} else {
res.status(200).json(space);
}
});
}
where: { "_id": space._id }
}, function(rows) {
fs.unlink(localFilePath, function(err) {
if (err) {
console.error(err);
res.status(400).json(err);
} else {
res.status(200).json(space);
}
});
});
}
});

View File

@ -1,9 +1,9 @@
<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"
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"
v-class="text-editing:(editing_artifact_id==a._id && (a.view.major_type=='text' || a.view.major_type=='shape'))"
@ -81,7 +81,7 @@
</div>
<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="icon icon-controls-play"></span>
</span>
@ -95,8 +95,8 @@
<span class="icon icon-controls-stop"></span>
</span>
<span class="tl-title" v-show="a.board.w>=250 && a.board.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-title" v-show="a.w>=250 && a.h>=150">{{a.view.filename}}</span>
<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-outpoint">{{a.player_view.total_time_string}}</span>
</span>

View File

@ -1,7 +1,7 @@
<div id="pick-mobile" v-if="active_space" class="dialog-section" v-show="opened_dialog=='mobile'">
<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">
Install the Spacedeck App on your phone and scan this QR code to upload photos, sound, video or text.