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) - Vue.js: Frontend UI Framework (included)
- SQLite (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 and ffprobe (for video/audio conversion)
- ffmpeg (for video/audio conversion)
- audiowaveform (for audio waveform rendering) (https://github.com/bbcrd/audiowaveform) - 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. 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.disable('x-powered-by');
app.use(helmet.noSniff()) app.use(helmet.noSniff())
// CUSTOM MIDDLEWARES
//app.use(require("./middlewares/error_helpers")); //app.use(require("./middlewares/error_helpers"));
app.use(require("./middlewares/session")); app.use(require("./middlewares/session"));
//app.use(require("./middlewares/cors")); //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', require("./middlewares/space_helpers"));
app.use('/api/spaces/:id/artifacts/:artifact_id', require("./middlewares/artifact_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/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'); const spaceRouter = require('./routes/api/spaces');
app.use('/api/spaces', spaceRouter); app.use('/api/spaces', spaceRouter);
spaceRouter.use('/:id/artifacts', require('./routes/api/space_artifacts')); spaceRouter.use('/:id/artifacts', require('./routes/api/space_artifacts'));
spaceRouter.use('/:id/memberships', require('./routes/api/space_memberships')); 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/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/sessions', require('./routes/api/sessions'));
//app.use('/api/webgrabber', require('./routes/api/webgrabber')); //app.use('/api/webgrabber', require('./routes/api/webgrabber'));
@ -125,8 +121,6 @@ if (app.get('env') == 'development') {
module.exports = app; module.exports = app;
// CONNECT TO DATABASE // CONNECT TO DATABASE
//const mongoHost = process.env.MONGO_PORT_27017_TCP_ADDR || config.get('mongodb_host');
//mongoose.connect('mongodb://' + mongoHost + '/spacedeck');
db.init(); db.init();
// START WEBSERVER // START WEBSERVER

View File

@ -13,47 +13,9 @@ const db = require('../models/db');
const Sequelize = require('sequelize'); const Sequelize = require('sequelize');
const Op = Sequelize.Op; const Op = Sequelize.Op;
const fileExtensionMap = { const mime = require('mime-types');
".amr" : "audio/AMR", const fileType = require('file-type');
".ogg" : "audio/ogg", const readChunk = require('read-chunk');
".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 convertableImageTypes = [ const convertableImageTypes = [
"image/png", "image/png",
@ -73,7 +35,7 @@ const convertableVideoTypes = [
const convertableAudioTypes = [ const convertableAudioTypes = [
"application/ogg", "application/ogg",
"audio/AMR", "audio/amr",
"audio/3ga", "audio/3ga",
"audio/wav", "audio/wav",
"audio/3gpp", "audio/3gpp",
@ -132,7 +94,7 @@ function createWaveform(fileName, localFilePath, callback){
function convertVideo(fileName, filePath, codec, callback, progress_callback) { function convertVideo(fileName, filePath, codec, callback, progress_callback) {
var ext = path.extname(fileName); var ext = path.extname(fileName);
var presetMime = fileExtensionMap[ext]; var presetMime = mime.lookup(fileName);
var newExt = codec == "mp4" ? "mp4" : "ogv"; var newExt = codec == "mp4" ? "mp4" : "ogv";
var convertedPath = filePath + "." + newExt; var convertedPath = filePath + "." + newExt;
@ -190,7 +152,7 @@ function convertVideo(fileName, filePath, codec, callback, progress_callback) {
function convertAudio(fileName, filePath, codec, callback) { function convertAudio(fileName, filePath, codec, callback) {
var ext = path.extname(fileName); var ext = path.extname(fileName);
var presetMime = fileExtensionMap[ext]; var presetMime = mime.lookup(fileName);
var newExt = codec == "mp3" ? "mp3" : "ogg"; var newExt = codec == "mp3" ? "mp3" : "ogg";
var convertedPath = filePath + "." + newExt; var convertedPath = filePath + "." + newExt;
@ -227,22 +189,14 @@ function createThumbnailForVideo(fileName, filePath, callback) {
function getMime(fileName, filePath, callback) { function getMime(fileName, filePath, callback) {
var ext = path.extname(fileName); var ext = path.extname(fileName);
var presetMime = fileExtensionMap[ext]; var presetMime = mime.lookup(fileName);
if (presetMime) { if (presetMime) {
callback(null, presetMime); callback(null, presetMime);
} else { } else {
exec.execFile("file", ["-b","--mime-type", filePath], {}, function(error, stdout, stderr) { const buffer = readChunk.sync(filePath, 0, 4100);
console.log("file stdout: ",stdout); var mimeType = fileType(buffer);
if (stderr === '' && error == null) { callback(null, mimeType);
//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);
}
});
} }
} }
@ -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({ async.parallel({
small: function(callback){ small: function(callback){
resizeAndUpload(a, size, 320, fileName, imageFilePath, callback); resizeAndUpload(a, size, 320, fileName, imageFilePath, callback);
@ -289,13 +243,13 @@ var resizeAndUploadImage = function(a, mime, size, fileName, fileNameOrg, imageF
}, },
original: function(callback){ original: function(callback){
var s3Key = "s"+ a.space_id.toString() + "/a" + a._id + "/" + fileNameOrg; 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); callback(null, url);
}); });
} }
}, function(err, results) { }, function(err, results) {
a.state = "idle"; a.state = "idle";
a.mime = mime; a.mime = mimeType;
var stats = fs.statSync(originalFilePath); var stats = fs.statSync(originalFilePath);
a.payload_size = stats["size"]; a.payload_size = stats["size"];
@ -325,21 +279,21 @@ var resizeAndUploadImage = function(a, mime, size, fileName, fileNameOrg, imageF
module.exports = { module.exports = {
convert: function(a, fileName, localFilePath, payloadCallback, progress_callback) { convert: function(a, fileName, localFilePath, payloadCallback, progress_callback) {
getMime(fileName, localFilePath, function(err, mime){ getMime(fileName, localFilePath, function(err, mimeType){
console.log("[convert] fn: "+fileName+" local: "+localFilePath+" mime:", mime); console.log("[convert] fn: "+fileName+" local: "+localFilePath+" mimeType:", mimeType);
if (!err) { if (!err) {
if (convertableImageTypes.indexOf(mime) > -1) { if (convertableImageTypes.indexOf(mimeType) > -1) {
gm(localFilePath).size(function (err, size) { gm(localFilePath).size(function (err, size) {
console.log("[convert] gm:", err, size); console.log("[convert] gm:", err, size);
if (!err) { if (!err) {
if(mime == "application/pdf") { if(mimeType == "application/pdf") {
var firstImagePath = localFilePath + ".jpeg"; 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) { 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) { 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) { fs.unlink(firstImagePath, function (err) {
payloadCallback(err, a); 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 //gifs are buggy after convertion, so we should not convert them
var s3Key = "s"+ a.space_id.toString() + "/a" + a._id.toString() + "/" + fileName; var s3Key = "s"+ a.space_id.toString() + "/a" + a._id.toString() + "/" + fileName;
@ -361,7 +315,7 @@ module.exports = {
var stats = fs.statSync(localFilePath); var stats = fs.statSync(localFilePath);
a.state = "idle"; a.state = "idle";
a.mime = mime; a.mime = mimeType;
a.payload_size = stats["size"]; a.payload_size = stats["size"];
a.payload_thumbnail_web_uri = url; a.payload_thumbnail_web_uri = url;
@ -389,12 +343,12 @@ module.exports = {
}); });
} else { } else {
resizeAndUploadImage(a, mime, size, fileName, fileName, localFilePath, localFilePath, payloadCallback); resizeAndUploadImage(a, mimeType, size, fileName, fileName, localFilePath, localFilePath, payloadCallback);
} }
} else payloadCallback(err); } else payloadCallback(err);
}); });
} else if (convertableVideoTypes.indexOf(mime) > -1) { } else if (convertableVideoTypes.indexOf(mimeType) > -1) {
async.parallel({ async.parallel({
thumbnail: function(callback) { thumbnail: function(callback) {
createThumbnailForVideo(fileName, localFilePath, function(err, created){ createThumbnailForVideo(fileName, localFilePath, function(err, created){
@ -410,7 +364,7 @@ module.exports = {
}); });
}, },
ogg: function(callback) { ogg: function(callback) {
if (mime == "video/ogg") { if (mimeType == "video/ogg") {
callback(null, "org"); callback(null, "org");
} else { } else {
convertVideo(fileName, localFilePath, "ogg", function(err, file) { convertVideo(fileName, localFilePath, "ogg", function(err, file) {
@ -426,7 +380,7 @@ module.exports = {
} }
}, },
mp4: function(callback) { mp4: function(callback) {
if (mime == "video/mp4") { if (mimeType == "video/mp4") {
callback(null, "org"); callback(null, "org");
} else { } else {
convertVideo(fileName, localFilePath, "mp4", function(err, file) { convertVideo(fileName, localFilePath, "mp4", function(err, file) {
@ -442,7 +396,7 @@ module.exports = {
} }
}, },
original: function(callback){ original: function(callback){
uploader.uploadFile(fileName, mime, localFilePath, function(err, url){ uploader.uploadFile(fileName, mimeType, localFilePath, function(err, url){
callback(null, url); callback(null, url);
}); });
} }
@ -452,7 +406,7 @@ module.exports = {
if (err) payloadCallback(err, a); if (err) payloadCallback(err, a);
else { else {
a.state = "idle"; a.state = "idle";
a.mime = mime; a.mime = mimeType;
var stats = fs.statSync(localFilePath); var stats = fs.statSync(localFilePath);
a.payload_size = stats["size"]; a.payload_size = stats["size"];
@ -461,7 +415,7 @@ module.exports = {
a.payload_thumbnail_big_uri = results.thumbnail; a.payload_thumbnail_big_uri = results.thumbnail;
a.payload_uri = results.original; a.payload_uri = results.original;
if (mime == "video/mp4") { if (mimeType == "video/mp4") {
a.payload_alternatives = [ a.payload_alternatives = [
{ {
mime: "video/ogg", mime: "video/ogg",
@ -497,7 +451,7 @@ module.exports = {
} }
}); });
} else if (convertableAudioTypes.indexOf(mime) > -1) { } else if (convertableAudioTypes.indexOf(mimeType) > -1) {
async.parallel({ async.parallel({
ogg: function(callback) { ogg: function(callback) {
@ -535,7 +489,7 @@ module.exports = {
}, },
original: function(callback) { original: function(callback) {
var keyName = "s" + a.space_id.toString() + "/a" + a._id.toString() + "/" + fileName; 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); callback(null, url);
}); });
} }
@ -546,7 +500,7 @@ module.exports = {
else { else {
a.state = "idle"; a.state = "idle";
a.mime = mime; a.mime = mimeType;
var stats = fs.statSync(localFilePath); var stats = fs.statSync(localFilePath);
a.payload_size = stats["size"]; a.payload_size = stats["size"];
@ -579,12 +533,12 @@ module.exports = {
} else { } 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; 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.state = "idle";
a.mime = mime; a.mime = mimeType;
var stats = fs.statSync(localFilePath); var stats = fs.statSync(localFilePath);
a.payload_size = stats["size"]; a.payload_size = stats["size"];
a.payload_uri = url; a.payload_uri = url;

View File

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

View File

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

View File

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

View File

@ -640,13 +640,10 @@ var SpacedeckSpaces = {
this.download_space_as_pdf(this.active_space); this.download_space_as_pdf(this.active_space);
} else if (e == "ZIP") { } else if (e == "ZIP") {
this.download_space_as_zip(this.active_space); this.download_space_as_zip(this.active_space);
}else if (e == "TXT"){
this.download_space_as_list(this.active_space);
} }
}.bind(this), { }.bind(this), {
button_1: "PDF", button_1: "PDF",
button_2: "ZIP", button_2: "ZIP",
button_3: "TXT",
button_cancel:__("cancel") button_cancel:__("cancel")
}); });

View File

@ -1,6 +1,8 @@
"use strict"; "use strict";
var config = require('config'); var config = require('config');
const os = require('os');
const db = require('../../models/db'); const db = require('../../models/db');
const Sequelize = require('sequelize'); const Sequelize = require('sequelize');
const Op = Sequelize.Op; 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, ''); var fileName = (req.query.filename || "upload.bin").replace(/[^a-zA-Z0-9_\-\.]/g, '');
// FIXME TODO use portable tmp dir var localFilePath = os.tmpdir() + "/" + 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);

View File

@ -1,6 +1,6 @@
"use strict"; "use strict";
var config = require('config'); var config = require('config');
require('../../models/db'); const db = require('../../models/db');
var redis = require('../../helpers/redis'); var redis = require('../../helpers/redis');
var mailer = require('../../helpers/mailer'); var mailer = require('../../helpers/mailer');
@ -49,18 +49,10 @@ 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) {
@ -68,7 +60,7 @@ router.get('/png', function(req, res, next) {
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 +68,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 +110,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,9 +243,10 @@ 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));
}); });

View File

@ -3,6 +3,7 @@ var config = require('config');
const db = require('../../models/db'); const db = require('../../models/db');
const Sequelize = require('sequelize'); const Sequelize = require('sequelize');
const Op = Sequelize.Op; 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');
@ -55,13 +56,15 @@ router.post('/', function(req, res, next) {
var attrs = req.body; var attrs = req.body;
attrs['space'] = req.space._id; attrs['space'] = 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) { }}, function(user) {
if (user) { if (user) {
membership.user = user; membership.user = user;
@ -70,9 +73,7 @@ router.post('/', function(req, res, next) {
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);
else {
var accept_link = config.endpoint + "/accept/" + membership._id + "?code=" + membership.code; var accept_link = config.endpoint + "/accept/" + membership._id + "?code=" + membership.code;
if (user) { if (user) {
@ -98,7 +99,6 @@ router.post('/', function(req, res, next) {
}); });
res.status(201).json(membership); res.status(201).json(membership);
}
}); });
}); });

View File

@ -1,6 +1,9 @@
"use strict"; "use strict";
var config = require('config'); 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 redis = require('../../helpers/redis');
var mailer = require('../../helpers/mailer'); var mailer = require('../../helpers/mailer');
@ -17,9 +20,7 @@ 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,16 +50,18 @@ 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) { }})
//.populate('user', userMapping)
.then(function(messages) {
res.status(200).json(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;
@ -66,65 +69,24 @@ router.post('/', function(req, res, next) {
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 { db.Message.create(msg, function() {
if (msg.message.length <= 1) return; if (msg.message.length <= 1) return;
// TODO reimplement notifications
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); 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) { }}, function(msg) {
if (!msg) { if (!msg) {
res.sendStatus(404); res.sendStatus(404);
} else { } else {
msg.remove(function(err) { msg.destroy(function(err) {
if (err) res.status(400).json(err); if (err) res.status(400).json(err);
else { else {
if (msg) { if (msg) {

View File

@ -114,32 +114,33 @@ router.get('/', function(req, res, next) {
}); });
} else if (req.query.search) { } else if (req.query.search) {
Membership.find({ db.Membership.findAll({where:{
user: req.user._id user: 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: {
"$or": [{"creator_id": req.user._id},
{"_id": {"$in": spaceIds}}, {"_id": {"$in": spaceIds}},
{"parent_space_id": {"$in": spaceIds}}], {"parent_space_id": {"$in": spaceIds}}],
name: new RegExp(req.query.search, "i") name: new RegExp(req.query.search, "i")}
}; };
Space db.Space
.find(q) .findAll(q)
.populate('creator', userMapping) //.populate('creator', userMapping)
.exec(function(err, spaces) { .then(function(spaces) {
if (err) console.error(err); 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();
@ -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) { } 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) {
db.getUserRoleInSpace(space, req.user, function(role) { db.getUserRoleInSpace(space, req.user, function(role) {
if (role == "none") { if (role == "none") {
if(space.access_mode == "public") { if(space.access_mode == "public") {
role = "viewer"; role = "viewer";
@ -167,12 +167,12 @@ router.get('/', function(req, res, next) {
} }
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
}) }})
.populate('creator', userMapping) //.populate('creator', userMapping)
.exec(function(err, spaces) { .then(function(spaces) {
res.status(200).json(spaces); res.status(200).json(spaces);
}); });
} else { } else {
@ -185,9 +185,6 @@ router.get('/', function(req, res, next) {
}); });
} else { } else {
console.log("!!!!!!!!!! spaces lookup");
db.Membership.findAll({ where: { db.Membership.findAll({ where: {
user_id: req.user._id user_id: req.user._id
}}).then(memberships => { }}).then(memberships => {
@ -243,9 +240,6 @@ router.post('/', function(req, res, next) {
db.Space.create(attrs).then(createdSpace => { db.Space.create(attrs).then(createdSpace => {
//if (err) res.sendStatus(400); //if (err) res.sendStatus(400);
console.log("!!!!!!!!!! createdSpace:",createdSpace);
var membership = { var membership = {
_id: uuidv4(), _id: uuidv4(),
user_id: req.user._id, user_id: req.user._id,
@ -342,7 +336,7 @@ router.put('/:id', function(req, res) {
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);
@ -351,29 +345,18 @@ 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.error("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": { where: { "_id": space._id }
advanced: adv }, function(rows) {
}
}, {
"new": true
}, function(err, space) {
if (err) {
res.sendStatus(400);
} else {
fs.unlink(localFilePath, function(err) { fs.unlink(localFilePath, function(err) {
if (err) { if (err) {
console.error(err); console.error(err);
@ -382,7 +365,6 @@ router.post('/:id/background', function(req, res, next) {
res.status(200).json(space); 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 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 @@
</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 @@
<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>

View File

@ -1,7 +1,7 @@
<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.