Compare commits

...

9 Commits

Author SHA1 Message Date
Lukas F. Hartmann
4012ee0c1c remove old models; update README 2018-04-12 18:32:57 +02:00
Lukas F. Hartmann
012a76ee1f fix memberships, zones 2018-04-12 18:19:05 +02:00
Lukas F. Hartmann
86bd276d21 fix importer es6 syntax incompat. with electron 2018-04-12 17:46:40 +02:00
Lukas F. Hartmann
76f85aa538 first version of import GUI 2018-04-12 17:41:22 +02:00
Lukas F. Hartmann
08b81d5ff4 port most backend functionality, further cleanups, basic electron support 2018-04-12 16:38:48 +02:00
Lukas F. Hartmann
8dc48a84ba further porting, cleanups, artifact upload/conversion and space screenshots 2018-04-11 21:14:00 +02:00
Lukas F. Hartmann
c549fcf9ec remove surplus docker-compose.yml 2018-04-11 20:06:11 +02:00
Lukas F. Hartmann
9ff1c39e89 adjust readme 2018-04-11 20:04:36 +02:00
Lukas F. Hartmann
960a4d6866 WIP first partially working version without mongodb, using sqlite/sequelize 2018-04-11 19:59:18 +02:00
62 changed files with 1725 additions and 3024 deletions

View File

@ -2,7 +2,7 @@
This is the free and open source version of Spacedeck, a web based, real time, collaborative whiteboard application with rich media support. Spacedeck was developed in 6 major releases during Autumn 2011 until the end of 2016 and was originally a commercial SaaS. The developers were Lukas F. Hartmann (mntmn) and Martin GĂĽther (magegu). All icons and large parts of the CSS were designed by Thomas Helbig (dergraph). This is the free and open source version of Spacedeck, a web based, real time, collaborative whiteboard application with rich media support. Spacedeck was developed in 6 major releases during Autumn 2011 until the end of 2016 and was originally a commercial SaaS. The developers were Lukas F. Hartmann (mntmn) and Martin GĂĽther (magegu). All icons and large parts of the CSS were designed by Thomas Helbig (dergraph).
As we plan to retire the subscription based service at spacedeck.com in late 2017, we decided to open-source Spacedeck to allow educational and other organizations who currently rely on Spacedeck to migrate to a self-hosted version. As we plan to retire the subscription based service at spacedeck.com in May 2018, we decided to open-source Spacedeck to allow educational and other organizations who currently rely on Spacedeck to migrate to a self-hosted or local version.
Data migration features will be added soon. Data migration features will be added soon.
@ -23,20 +23,21 @@ We appreciate filed issues, pull requests and general discussion.
Spacedeck uses the following major building blocks: Spacedeck uses the following major building blocks:
- Vue.js: Frontend UI Framework - Node.js 9.x: Web Server / API
- Node.js 7.x: Web Server / API - Vue.js: Frontend UI Framework (included)
- MongoDB 3.4: Data store *(important: newer versions than 3.4 don't work yet!)* - SQLite (included)
- Redis 3.x: Data store for realtime channels, (*optional*)
It also has some binary dependencies for media conversion and PDF export: It also has some optional binary dependencies for advanced media conversion:
- imagemagick, graphicsmagick, libav(+codecs, ffmpeg replacement), audiowaveform (https://github.com/bbcrd/audiowaveform), phantomjs (http://phantomjs.org/) - ffmpeg and ffprobe (for video/audio conversion)
- audiowaveform (for audio waveform rendering) (https://github.com/bbcrd/audiowaveform)
- 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.
Optionally, you can use Amazon S3 for file storage. In that case you need an Amazon AWS account and have the ```AWS_ACCESS_KEY_ID``` and ```AWS_SECRET_ACCESS_KEY``` environment variables defined. For sending emails in production, Amazon SES is required. To use Spacedeck, you only need Node.JS 9.x.
To run Spacedeck, you need Node.JS 7.x and a running MongoDB 3.4 instance. Then, to install all node dependencies, run Then, to install all node dependencies, run
npm install npm install
@ -48,18 +49,25 @@ To rebuild the frontend CSS styles (you need to do this at least once):
See [config/default.json](config/default.json) See [config/default.json](config/default.json)
# Run # Run (web server)
export NODE_ENV=development node spacedeck.js
npm start
open http://localhost:9666 Then open http://localhost:9666 in a web browser.
# Run (desktop app with integrated web server)
electron .
# License # License
Spacedeck Open is released under the GNU Affero General Public License Version 3 (GNU AGPLv3). The Spacedeck logo and brand assets are registered trademarks of Spacedeck GmbH. All rights reserved.
Spacedeck Open source code is released under the GNU Affero General Public License Version 3 (GNU AGPLv3).
Spacedeck Open - Web-based Collaborative Whiteboard For Rich Media Spacedeck Open - Web-based Collaborative Whiteboard For Rich Media
Copyright (C) 2011-2017 Lukas F. Hartmann, Martin GĂĽther, Thomas Helbig Copyright (C) 2011-2018 Lukas F. Hartmann, Martin GĂĽther
Icons and original CSS design copyright by Thomas Helbig
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as it under the terms of the GNU Affero General Public License as

197
app.js
View File

@ -1,180 +1,33 @@
"use strict"; const spacedeck = require('./spacedeck')
require('./models/schema'); const electron = require('electron')
require("log-timestamp"); const electronApp = electron.app
const BrowserWindow = electron.BrowserWindow
let mainWindow
const config = require('config'); function createWindow () {
const redis = require('./helpers/redis'); mainWindow = new BrowserWindow({width: 1200, height: 700})
const websockets = require('./helpers/websockets'); mainWindow.loadURL("http://localhost:9666")
mainWindow.on('closed', function () {
const http = require('http'); mainWindow = null
const path = require('path'); })
const _ = require('underscore');
const favicon = require('serve-favicon');
const logger = require('morgan');
const cookieParser = require('cookie-parser');
const bodyParser = require('body-parser');
const mongoose = require('mongoose');
const swig = require('swig');
const i18n = require('i18n-2');
const helmet = require('helmet');
const express = require('express');
const app = express();
const serveStatic = require('serve-static');
const isProduction = app.get('env') === 'production';
console.log("Booting Spacedeck Open… (environment: " + app.get('env') + ")");
app.use(logger(isProduction ? 'combined' : 'dev'));
i18n.expressBind(app, {
locales: ["en", "de", "fr"],
defaultLocale: "en",
cookieName: "spacedeck_locale",
devMode: (app.get('env') == 'development')
});
swig.setDefaults({
varControls: ["[[", "]]"] // otherwise it's not compatible with vue.js
});
swig.setFilter('cdn', function(input, idx) {
return input;
});
app.engine('html', swig.renderFile);
app.set('view engine', 'html');
if (isProduction) {
app.set('views', path.join(__dirname, 'build', 'views'));
app.use(favicon(path.join(__dirname, 'build', 'assets', 'images', 'favicon.png')));
app.use(express.static(path.join(__dirname, 'build', 'assets')));
} else {
app.set('views', path.join(__dirname, 'views'));
app.use(favicon(path.join(__dirname, 'public', 'images', 'favicon.png')));
app.use(express.static(path.join(__dirname, 'public')));
} }
app.use(bodyParser.json({ electronApp.on('ready', createWindow)
limit: '50mb'
}));
app.use(bodyParser.urlencoded({ // Quit when all windows are closed.
extended: false, electronApp.on('window-all-closed', function () {
limit: '50mb' // On OS X it is common for applications and their menu bar
})); // to stay active until the user quits explicitly with Cmd + Q
if (process.platform !== 'darwin') {
app.use(cookieParser()); electronApp.quit()
app.use(helmet.frameguard())
app.use(helmet.xssFilter())
app.use(helmet.hsts({
maxAge: 7776000000,
includeSubdomains: true
}))
app.disable('x-powered-by');
app.use(helmet.noSniff())
// CUSTOM MIDDLEWARES
app.use(require("./middlewares/templates"));
app.use(require("./middlewares/error_helpers"));
app.use(require("./middlewares/setuser"));
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/teams', require("./middlewares/team_helpers"));
// REAL ROUTES
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/teams', require('./routes/api/teams'));
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 electronApp.on('activate', function () {
app.use(require('./middlewares/404')); // On OS X it's common to re-create a window in the app when the
if (app.get('env') == 'development') { // dock icon is clicked and there are no other windows open.
app.set('view cache', false); if (mainWindow === null) {
swig.setDefaults({cache: false}); createWindow()
} else {
app.use(require('./middlewares/500'));
} }
})
module.exports = app;
// CONNECT TO DATABASE
const mongoHost = process.env.MONGO_PORT_27017_TCP_ADDR || config.get('mongodb_host');
mongoose.connect('mongodb://' + mongoHost + '/spacedeck');
// 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 & WORKER
websockets.startWebsockets(server);
redis.connectRedis();
process.on('message', (message) => {
console.log("Process message:", message);
if (message === 'shutdown') {
console.log("Exiting spacedeck.");
process.exit(0);
}
});

View File

@ -1,34 +0,0 @@
version: '2'
services:
sync:
image: redis
storage:
image: minio/minio
environment:
- MINIO_ACCESS_KEY=AKIAIOSFODNN7EXAMPLE
- MINIO_SECRET_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
ports:
- 9123:9000
command: server /export
db:
image: mongo
spacedeck-open:
environment:
- env=development
- MINIO_ACCESS_KEY=AKIAIOSFODNN7EXAMPLE
- MINIO_SECRET_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
build: .
volumes:
# - ./:/usr/src/app
- /usr/src/app/node_modules
command: npm start
ports:
- 9666:9666
depends_on:
- db
- sync
- storage
links:
- storage
- db
- sync

View File

@ -4,52 +4,18 @@ const exec = require('child_process');
const gm = require('gm'); const gm = require('gm');
const async = require('async'); const async = require('async');
const fs = require('fs'); const fs = require('fs');
const Models = require('../models/schema'); const Models = require('../models/db');
const uploader = require('../helpers/uploader'); const uploader = require('../helpers/uploader');
const path = require('path'); const path = require('path');
const os = require('os'); const os = require('os');
const fileExtensionMap = { const db = require('../models/db');
".amr" : "audio/AMR", const Sequelize = require('sequelize');
".ogg" : "audio/ogg", const Op = Sequelize.Op;
".aac" : "audio/aac",
".mp3" : "audio/mpeg", const mime = require('mime-types');
".mpg" : "video/mpeg", const fileType = require('file-type');
".3ga" : "audio/3ga", const readChunk = require('read-chunk');
".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",
@ -69,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",
@ -128,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;
@ -186,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;
@ -223,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);
}
});
} }
} }
@ -272,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);
@ -285,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"];
@ -301,15 +259,11 @@ var resizeAndUploadImage = function(a, mime, size, fileName, fileNameOrg, imageF
a.payload_uri = results.original; a.payload_uri = results.original;
var factor = 320/size.width; var factor = 320/size.width;
var newBoardSpecs = a.board; a.w = Math.round(size.width*factor);
newBoardSpecs.w = Math.round(size.width*factor); a.h = Math.round(size.height*factor);
newBoardSpecs.h = Math.round(size.height*factor);
a.board = newBoardSpecs;
a.updated_at = new Date(); a.updated_at = new Date();
a.save(function(err) { a.save().then(function() {
if(err) payloadCallback(err, null);
else {
fs.unlink(originalFilePath, function (err) { fs.unlink(originalFilePath, function (err) {
if (err){ if (err){
console.error(err); console.error(err);
@ -319,28 +273,27 @@ var resizeAndUploadImage = function(a, mime, size, fileName, fileNameOrg, imageF
payloadCallback(null, a); payloadCallback(null, a);
} }
}); });
}
}); });
}); });
}; };
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);
}); });
@ -350,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;
@ -362,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;
@ -371,15 +324,11 @@ module.exports = {
a.payload_uri = url; a.payload_uri = url;
var factor = 320/size.width; var factor = 320/size.width;
var newBoardSpecs = a.board; a.w = Math.round(size.width*factor);
newBoardSpecs.w = Math.round(size.width*factor); a.h = Math.round(size.height*factor);
newBoardSpecs.h = Math.round(size.height*factor);
a.board = newBoardSpecs;
a.updated_at = new Date(); a.updated_at = new Date();
a.save(function(err){ a.save().then(function() {
if(err) payloadCallback(err, null);
else {
fs.unlink(localFilePath, function (err) { fs.unlink(localFilePath, function (err) {
if (err){ if (err){
console.error(err); console.error(err);
@ -389,18 +338,17 @@ module.exports = {
payloadCallback(null, a); payloadCallback(null, a);
} }
}); });
}
}); });
} }
}); });
} 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){
@ -416,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) {
@ -432,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) {
@ -448,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);
}); });
} }
@ -458,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"];
@ -467,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",
@ -483,6 +431,8 @@ module.exports = {
]; ];
} }
db.packArtifact(a);
a.updated_at = new Date(); a.updated_at = new Date();
a.save(function(err) { a.save(function(err) {
if (err) payloadCallback(err, null); if (err) payloadCallback(err, null);
@ -501,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) {
@ -539,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);
}); });
} }
@ -550,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"];
@ -564,9 +514,10 @@ module.exports = {
]; ];
a.updated_at = new Date(); a.updated_at = new Date();
a.save(function(err){
if(err) payloadCallback(err, null); db.packArtifact(a);
else {
a.save().then(function(){
fs.unlink(localFilePath, function (err) { fs.unlink(localFilePath, function (err) {
if (err){ if (err){
console.error(err); console.error(err);
@ -576,31 +527,27 @@ module.exports = {
payloadCallback(null, a); payloadCallback(null, a);
} }
}); });
}
}); });
} }
}); });
} 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;
a.updated_at = new Date(); a.updated_at = new Date();
a.save(function(err) { a.save().then(function() {
if(err) payloadCallback(err, null);
else {
fs.unlink(localFilePath, function (err) { fs.unlink(localFilePath, function (err) {
payloadCallback(null, a); payloadCallback(null, a);
}); });
}
}); });
}); });
} }

View File

@ -5,7 +5,12 @@ const config = require('config')
const fs = require('fs') const fs = require('fs')
const path = require('path') const path = require('path')
require('../models/schema') const db = require('../models/db')
const Sequelize = require('sequelize')
const Op = Sequelize.Op
const uuidv4 = require('uuid/v4')
require('../models/db')
module.exports = { module.exports = {
importZIP: function(user, zipPath) { importZIP: function(user, zipPath) {
@ -54,8 +59,8 @@ module.exports = {
let artifacts = JSON.parse(fs.readFileSync(importDir+'/'+space._id+'_artifacts.json')) let artifacts = JSON.parse(fs.readFileSync(importDir+'/'+space._id+'_artifacts.json'))
console.log('[import] space',space._id,'artifacts:',artifacts.length) console.log('[import] space',space._id,'artifacts:',artifacts.length)
let q = {_id: space._id} //let q = {where: {_id: space._id}}
space.creator = user._id space.creator_id = user._id
delete space.__v delete space.__v
// transplant homefolder // transplant homefolder
@ -64,18 +69,36 @@ module.exports = {
space.parent_space_id = user.home_folder_id space.parent_space_id = user.home_folder_id
} }
Space.findOneAndUpdate(q, space, {upsert: true}, function(err,res) { // move nested attrs
if (err) console.log("[import] space upsert err:",err) console.log(space)
for (k in space.advanced) {
space[k] = space.advanced[k]
}
db.Space.create(space)
.error((err) => {
console.error("[import] space upsert err:",err)
}) })
for (var j=0; j<artifacts.length; j++) { for (var j=0; j<artifacts.length; j++) {
let a = artifacts[j] let a = artifacts[j]
let q = {_id: a._id} let q = {_id: a._id}
a.creator = user._id a.user_id = user._id
delete a.__v delete a.__v
delete a.payload_thumbnail_big_uri delete a.payload_thumbnail_big_uri
// move nested attrs
for (k in a.style) {
a[k] = a.style[k]
}
for (k in a.meta) {
a[k] = a.meta[k]
}
for (k in a.board) {
a[k] = a.board[k]
}
let prefix = "/storage/"+relativeImportDir+"/"+space._id+"_files/" let prefix = "/storage/"+relativeImportDir+"/"+space._id+"_files/"
if (a.thumbnail_uri && a.thumbnail_uri[0]!='/') a.thumbnail_uri = prefix + a.thumbnail_uri if (a.thumbnail_uri && a.thumbnail_uri[0]!='/') a.thumbnail_uri = prefix + a.thumbnail_uri
if (a.payload_uri && a.payload_uri[0]!='/') a.payload_uri = prefix + a.payload_uri if (a.payload_uri && a.payload_uri[0]!='/') a.payload_uri = prefix + a.payload_uri
@ -92,8 +115,10 @@ module.exports = {
} }
} }
Artifact.findOneAndUpdate(q, a, {upsert: true}, function(err,res) { db.packArtifact(a)
if (err) console.log("[import] artifact upsert err:",err)
db.Artifact.create(a).error(function(err) {
console.error("[import] artifact upsert err:",err)
}) })
} }
} }

View File

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

View File

@ -1,8 +1,9 @@
'use strict'; 'use strict';
require('../models/schema'); 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

@ -1,14 +1,16 @@
'use strict'; 'use strict';
require('../models/schema');
const db = require('../models/db');
const Sequelize = require('sequelize');
const Op = Sequelize.Op;
const config = require('config'); const config = require('config');
const WebSocketServer = require('ws').Server; const WebSocketServer = require('ws').Server;
const RedisConnection = require('ioredis'); //const RedisConnection = require('ioredis');
const async = require('async'); const async = require('async');
const _ = require("underscore"); const _ = require("underscore");
const mongoose = require("mongoose");
const crypto = require('crypto'); const crypto = require('crypto');
const redisMock = require("./redis.js"); const redisMock = require("./redis.js");
@ -45,11 +47,11 @@ module.exports = {
const editorAuth = msg.editor_auth; const editorAuth = msg.editor_auth;
const spaceId = msg.space_id; const spaceId = msg.space_id;
Space.findOne({"_id": spaceId}).populate('creator').exec((err, space) => { db.Space.findOne({where: {"_id": spaceId}}).then(space => {
if (space) { if (space) {
const upgradeSocket = function() { const upgradeSocket = function() {
if (token) { if (token) {
User.findBySessionToken(token, function(err, user) { db.findUserBySessionToken(token, function(err, user) {
if (err) { if (err) {
console.error(err, user); console.error(err, user);
} else { } else {
@ -271,7 +273,7 @@ module.exports = {
if (!spaceId) if (!spaceId)
return; return;
this.state.smembers("space_" + spaceId, function(err, list) { /*this.state.smembers("space_" + spaceId, function(err, list) {
async.map(list, function(item, callback) { async.map(list, function(item, callback) {
this.state.get(item, function(err, userId) { this.state.get(item, function(err, userId) {
console.log(item, "->", userId); console.log(item, "->", userId);
@ -292,16 +294,14 @@ module.exports = {
return {nickname: realNickname, email: null, avatar_thumbnail_uri: null }; return {nickname: realNickname, email: null, avatar_thumbnail_uri: null };
}); });
User.find({"_id" : { "$in" : validUserIds }}, { "nickname" : 1 , "email" : 1, "avatar_thumbnail_uri": 1 }, function(err, users) { db.User.findAll({where: {
if (err) "_id" : { "$in" : validUserIds }}, attributes: ["nickname","email","avatar_thumbnail_uri"]})
console.error(err); .then(users) {
else {
const allUsers = users.concat(anonymousUsers); const allUsers = users.concat(anonymousUsers);
const strUsers = JSON.stringify({users: allUsers, space_id: spaceId}); const strUsers = JSON.stringify({users: allUsers, space_id: spaceId});
this.state.publish("users", strUsers); this.state.publish("users", strUsers);
}
}.bind(this));
}.bind(this)); }.bind(this));
}.bind(this)); }.bind(this));
}.bind(this));*/
} }
}; };

View File

@ -1,6 +1,6 @@
'use strict'; 'use strict';
require('../models/schema'); require('../models/db');
var config = require('config'); var config = require('config');
module.exports = (req, res, next) => { module.exports = (req, res, next) => {

View File

@ -1,9 +1,11 @@
'use strict'; 'use strict';
require('../models/schema'); require('../models/db');
var config = require('config'); var config = require('config');
const redis = require('../helpers/redis'); const redis = require('../helpers/redis');
// FIXME TODO object.toJSON()
var saveAction = (actionKey, object) => { var saveAction = (actionKey, object) => {
if (object.constructor.modelName == "Space") if (object.constructor.modelName == "Space")
return; return;
@ -13,14 +15,14 @@ var saveAction = (actionKey, object) => {
space: object.space_id || object.space, space: object.space_id || object.space,
user: object.user_id || object.user, user: object.user_id || object.user,
editor_name: object.editor_name, editor_name: object.editor_name,
object: object.toJSON() object: object
}; };
let action = new Action(attr); /*let action = new Action(attr);
action.save(function(err) { action.save(function(err) {
if (err) if (err)
console.error("saved create action err:", err); console.error("saved create action err:", err);
}); });*/
}; };
module.exports = (req, res, next) => { module.exports = (req, res, next) => {
@ -32,21 +34,21 @@ module.exports = (req, res, next) => {
res['distributeCreate'] = function(model, object) { res['distributeCreate'] = function(model, object) {
if (!object) return; if (!object) return;
redis.sendMessage("create", model, object.toJSON(), req.channelId); redis.sendMessage("create", model, object, req.channelId);
this.status(201).json(object.toJSON()); this.status(201).json(object);
saveAction("create", object); saveAction("create", object);
}; };
res['distributeUpdate'] = function(model, object) { res['distributeUpdate'] = function(model, object) {
if (!object) return; if (!object) return;
redis.sendMessage("update", model, object.toJSON(), req.channelId); redis.sendMessage("update", model, object, req.channelId);
this.status(200).json(object.toJSON()); this.status(200).json(object);
saveAction("update", object); saveAction("update", object);
}; };
res['distributeDelete'] = function(model, object) { res['distributeDelete'] = function(model, object) {
if (!object) return; if (!object) return;
redis.sendMessage("delete", model, object.toJSON(), req.channelId); redis.sendMessage("delete", model, object, req.channelId);
this.sendStatus(204); this.sendStatus(204);
saveAction("delete", object); saveAction("delete", object);
}; };

View File

@ -1,22 +1,20 @@
'use strict'; 'use strict';
const db = require('../models/db');
const Sequelize = require('sequelize');
const Op = Sequelize.Op;
require('../models/schema');
var config = require('config'); var config = require('config');
module.exports = (req, res, next) => { module.exports = (req, res, next) => {
var artifactId = req.params.artifact_id; var artifactId = req.params.artifact_id;
Artifact.findOne({ db.Artifact.findOne({where: {
"_id": artifactId "_id": artifactId
}, (err, artifact) => { }}).then(artifact => {
if (err) {
res.status(400).json(err);
} else {
if (artifact) { if (artifact) {
req['artifact'] = artifact; req['artifact'] = artifact;
next(); next();
} else { } else {
res.sendStatus(404); res.sendStatus(404);
} }
}
}); });
}; };

View File

@ -1,6 +1,6 @@
'use strict'; 'use strict';
require('../models/schema'); require('../models/db');
const config = require('config'); const config = require('config');
const url = require('url'); const url = require('url');
@ -33,13 +33,13 @@ module.exports = (req, res, next) => {
respond(origin, req, res, next); respond(origin, req, res, next);
} else { } else {
Team.getTeamForHost(parsedUrl.hostname, (err, team, subdomain) => { //Team.getTeamForHost(parsedUrl.hostname, (err, team, subdomain) => {
if (team) { //if (team) {
respond(origin, req, res, next); respond(origin, req, res, next);
} else { //} else {
next(); next();
} //}
}); //});
} }
} else { } else {

View File

@ -1,6 +1,6 @@
'use strict'; 'use strict';
require('../models/schema'); require('../models/db');
var config = require('config'); var config = require('config');
module.exports = (req, res, next) => { module.exports = (req, res, next) => {
@ -10,8 +10,8 @@ module.exports = (req, res, next) => {
req.i18n.setLocaleFromCookie(); req.i18n.setLocaleFromCookie();
} }
if (req.user && req.user.preferences.language) { if (req.user && req.user.prefs_language) {
req.i18n.setLocale(req.user.preferences.language); req.i18n.setLocale(req.user.prefs_language);
} }
next(); next();
} }

46
middlewares/session.js Normal file
View File

@ -0,0 +1,46 @@
'use strict';
const db = require('../models/db');
var config = require('config');
module.exports = (req, res, next) => {
const token = req.cookies["sdsession"];
if (token && token != "null" && token != null) {
db.Session.findOne({where: {token: token}})
.then(session => {
if (!session) {
// session not found
next();
}
else db.User.findOne({where: {_id: session.user_id}})
.then(user => {
if (!user) {
res.clearCookie('sdsession');
if (req.accepts("text/html")) {
res.send("Please clear your cookies and try again.");
} else if (req.accepts('application/json')) {
res.status(403).json({
"error": "token_not_found"
});
} else {
res.send("Please clear your cookies and try again.");
}
} else {
req["token"] = token;
req["user"] = user;
next();
}
});
})
.error(err => {
console.error("Session resolve error",err);
next();
});
} else {
next();
}
}

View File

@ -1,35 +0,0 @@
'use strict';
require('../models/schema');
var config = require('config');
module.exports = (req, res, next) => {
const token = req.cookies["sdsession"];
if (token && token != "null" && token !== null) {
User.findOne({
"sessions.token": token
}).populate('team').exec((err, user) => {
if (err) console.error("session.token lookup error:",err);
if (!user) {
res.clearCookie('sdsession');
if (req.accepts("text/html")) {
res.send("Please clear your cookies and try again.");
} else if (req.accepts('application/json')) {
res.status(403).json({
"error": "token_not_found"
});
} else {
res.send("Please clear your cookies and try again.");
}
} else {
req["token"] = token;
req["user"] = user;
next();
}
});
} else {
next();
}
}

View File

@ -1,6 +1,6 @@
'use strict'; 'use strict';
require('../models/schema'); const db = require('../models/db');
var config = require('config'); var config = require('config');
module.exports = (req, res, next) => { module.exports = (req, res, next) => {
@ -19,50 +19,6 @@ module.exports = (req, res, next) => {
} }
}; };
var rolePerUser = (originalSpace, user, cb) => {
originalSpace.path = [];
if (originalSpace._id.equals(req.user.home_folder_id) || (originalSpace.creator && originalSpace.creator._id.equals(req.user._id))) {
cb("admin");
} else {
var findMembershipsForSpace = function(space, allMemberships, prevRole) {
Membership.find({
"space": space._id
}, function(err, parentMemberships) {
var currentMemberships = parentMemberships.concat(allMemberships);
if (space.parent_space_id) {
Space.findOne({
"_id": space.parent_space_id
}, function(err, parentSpace) {
findMembershipsForSpace(parentSpace, currentMemberships, prevRole);
});
} else {
// reached the top
var role = prevRole;
space.memberships = currentMemberships;
if(role == "none"){
if(originalSpace.access_mode == "public") {
role = "viewer";
}
}
currentMemberships.forEach(function(m, i) {
if (m.user && m.user.equals(user._id)) {
role = m.role;
}
});
cb(role);
}
});
};
findMembershipsForSpace(originalSpace, [], "none");
}
};
var finalizeAnonymousLogin = function(space, spaceAuth) { var finalizeAnonymousLogin = function(space, spaceAuth) {
var role = "none"; var role = "none";
@ -77,7 +33,7 @@ module.exports = (req, res, next) => {
} }
if (req.user) { if (req.user) {
rolePerUser(space, req.user, function(newRole) { db.getUserRoleInSpace(space, req.user, function(newRole) {
if (newRole == "admin" && (role == "editor" || role == "viewer")) { if (newRole == "admin" && (role == "editor" || role == "viewer")) {
finalizeReq(space, newRole); finalizeReq(space, newRole);
} else if (newRole == "editor" && (role == "viewer")) { } else if (newRole == "editor" && (role == "viewer")) {
@ -97,16 +53,17 @@ module.exports = (req, res, next) => {
'email': 1 'email': 1
}; };
Space.findOne({ db.Space.findOne({where: {
"_id": spaceId "_id": spaceId
}).populate("creator", userMapping).exec(function(err, space) { }}).then(function(space) {
if (err) {
res.status(400).json(err); //.populate("creator", userMapping)
} else { //if (err) {
// res.status(400).json(err);
//} else {
if (space) { if (space) {
if (space.access_mode == "public") { if (space.access_mode == "public") {
if (space.password) { if (space.password) {
if (req.spacePassword) { if (req.spacePassword) {
if (req.spacePassword === space.password) { if (req.spacePassword === space.password) {
@ -126,6 +83,8 @@ module.exports = (req, res, next) => {
} }
} else { } else {
// space is private
// special permission for screenshot/pdf export from backend // special permission for screenshot/pdf export from backend
if (req.query['api_token'] && req.query['api_token'] == config.get('phantom_api_secret')) { if (req.query['api_token'] && req.query['api_token'] == config.get('phantom_api_secret')) {
finalizeReq(space, "viewer"); finalizeReq(space, "viewer");
@ -133,7 +92,7 @@ module.exports = (req, res, next) => {
} }
if (req.user) { if (req.user) {
rolePerUser(space, req.user, function(role) { db.getUserRoleInSpace(space, req.user, function(role) {
if (role == "none") { if (role == "none") {
finalizeAnonymousLogin(space, req["spaceAuth"]); finalizeAnonymousLogin(space, req["spaceAuth"]);
} else { } else {
@ -155,6 +114,5 @@ module.exports = (req, res, next) => {
"error": "space_not_found" "error": "space_not_found"
}); });
} }
}
}); });
} }

View File

@ -1,33 +0,0 @@
'use strict';
require('../models/schema');
var config = require('config');
module.exports = (req, res, next) => {
let host = req.headers.host;
Team.getTeamForHost(host, (err, team, subdomain) => {
if (subdomain) {
if (!err && team) {
req.subdomainTeam = team;
req.subdomain = subdomain;
next()
} else {
if (req.accepts('text/html')) {
res.status(404).render('not_found', {
title: 'Page Not Found.'
});
} else if (req.accepts('application/json')) {
res.status(404).json({
"error": "not_found"
});
} else {
res.status(404).render('not_found', {
title: 'Page Not Found.'
});
}
}
} else {
next();
}
});
}

View File

@ -1,23 +0,0 @@
'use strict';
require('../models/schema');
var config = require('config');
module.exports = (req, res, next) => {
if (req.user) {
var isAdmin = req.user.team.admins.indexOf(req.user._id) >= 0;
var correctMethod = req.method == "GET" || (req.method == "DELETE" || req.method == "PUT" || req.method == "POST");
if (correctMethod && isAdmin) {
next();
} else {
res.status(403, {
"error": "not authorized"
});
}
} else {
res.status(403, {
"error": "not logged in"
});
}
}

View File

@ -1,31 +0,0 @@
'use strict';
require('../models/schema');
var config = require('config');
var _ = require('underscore');
module.exports = (req, res, next) => {
res.oldRender = res.render;
res.render = function(template, params) {
var team = req.subdomainTeam;
if (team) {
team = _.pick(team.toObject(), ['_id', 'name', 'subdomain', 'avatar_original_uri']);
} else {
team = null;
}
const addParams = {
locale: req.i18n.locale,
config: config,
subdomain_team: team,
user: req.user,
csrf_token: "",
socket_auth: req.token
};
const all = _.extend(params, addParams);
res.oldRender(template, all);
};
next();
}

View File

@ -1,5 +1,7 @@
'use strict'; 'use strict';
// FIXME port this last model
var mongoose = require('mongoose'); var mongoose = require('mongoose');
var Schema = mongoose.Schema; var Schema = mongoose.Schema;

View File

@ -1,88 +0,0 @@
'use strict';
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
module.exports.artifactSchema = Schema({
mime: String,
thumbnail_uri: String,
space_id: Schema.Types.ObjectId,
user_id: {type: Schema.Types.ObjectId, ref: 'User' },
last_update_user_id: {type: Schema.Types.ObjectId, ref: 'User' },
editor_name: String,
last_update_editor_name: String,
description: String,
state: {type: String, default: "idle"},
meta: {
linked_to: [String],
title: String,
tags: [String],
search_text: String,
link_uri: String,
play_from: Number,
play_to: Number,
},
board: {
x: {type: Number, default: 0.0},
y: {type: Number, default: 0.0},
z: {type: Number, default: 0.0},
r: {type: Number, default: 0.0},
w: {type: Number, default: 100},
h: {type: Number, default: 100},
},
control_points: [{
dx: Number, dy: Number
}],
group:{type: String, default: ""},
locked: {type: Boolean, default: false},
payload_uri: String,
payload_thumbnail_web_uri: String,
payload_thumbnail_medium_uri: String,
payload_thumbnail_big_uri: String,
payload_size: Number, // file size in bytes
style: {
fill_color: {type: String, default: "transparent"},
stroke_color:{type: String, default: "#000000"},
text_color: String,
stroke: {type: Number, default: 0.0},
stroke_style: {type: String, default: "solid"},
alpha: {type: Number, default: 1.0},
order: {type: Number, default: 0},
crop: {
x: Number,
y: Number,
w: Number,
h: Number
},
shape: String,
shape_svg: String,
padding_left: Number,
padding_right: Number,
padding_top: Number,
padding_bottom: Number,
margin_left: Number,
margin_right: Number,
margin_top: Number,
margin_bottom: Number,
border_radius: Number,
align: {type: String, default: "left"},
valign: {type: String, default: "top"},
brightness: Number,
contrast: Number,
saturation: Number,
blur: Number,
hue: Number,
opacity: Number
},
payload_alternatives: [{
mime: String,
payload_uri: String,
payload_thumbnail_web_uri: String,
payload_thumbnail_medium_uri: String,
payload_thumbnail_big_uri: String,
payload_size: Number
}],
created_at: {type: Date, default: Date.now},
created_from_ip: {type: String},
updated_at: {type: Date, default: Date.now}
});

347
models/db.js Normal file
View File

@ -0,0 +1,347 @@
//'use strict';
//var mongoose = require('mongoose');
//const sqlite3 = require('sqlite3').verbose();
function sequel_log(a,b,c) {
console.log(a);
}
const Sequelize = require('sequelize');
const sequelize = new Sequelize('database', 'username', 'password', {
host: 'localhost',
dialect: 'sqlite',
pool: {
max: 5,
min: 0,
acquire: 30000,
idle: 10000
},
// SQLite only
storage: 'database.sqlite',
logging: sequel_log,
// http://docs.sequelizejs.com/manual/tutorial/querying.html#operators
operatorsAliases: false
});
var User;
var Session;
var Space;
var Membership;
var Artifact;
var Message;
var Action;
module.exports = {
User: sequelize.define('user', {
_id: {type: Sequelize.STRING, primaryKey: true},
email: Sequelize.STRING,
password_hash: Sequelize.STRING,
nickname: Sequelize.STRING,
avatar_original_uri: Sequelize.STRING,
avatar_thumb_uri: Sequelize.STRING,
confirmation_token: Sequelize.STRING,
password_reset_token: Sequelize.STRING,
home_folder_id: Sequelize.STRING,
prefs_language: Sequelize.STRING,
prefs_email_notifications: Sequelize.STRING,
prefs_email_digest: Sequelize.STRING,
created_at: {type: Sequelize.DATE, defaultValue: Sequelize.NOW},
updated_at: {type: Sequelize.DATE, defaultValue: Sequelize.NOW}
}),
Session: sequelize.define('session', {
token: {type: Sequelize.STRING, primaryKey: true},
user_id: Sequelize.STRING,
expires: Sequelize.DATE,
created_at: {type: Sequelize.DATE, defaultValue: Sequelize.NOW},
device: Sequelize.STRING,
ip: Sequelize.STRING
}),
Space: sequelize.define('space', {
_id: {type: Sequelize.STRING, primaryKey: true},
name: {type: Sequelize.STRING, default: "New Space"},
space_type: {type: Sequelize.STRING, defaultValue: "space"},
creator_id: Sequelize.STRING,
parent_space_id: Sequelize.STRING,
access_mode: {type: Sequelize.STRING, default: "private"}, // "public" || "private"
password: Sequelize.STRING,
edit_hash: Sequelize.STRING,
edit_slug: Sequelize.STRING,
editors_locking: Sequelize.BOOLEAN,
thumbnail_uri: Sequelize.STRING,
width: Sequelize.INTEGER,
height: Sequelize.INTEGER,
background_color: Sequelize.STRING,
background_uri: Sequelize.STRING,
created_at: {type: Sequelize.DATE, defaultValue: Sequelize.NOW},
updated_at: {type: Sequelize.DATE, defaultValue: Sequelize.NOW},
thumbnail_url: Sequelize.STRING,
thumbnail_updated_at: {type: Sequelize.DATE}
}),
Membership: sequelize.define('membership', {
_id: {type: Sequelize.STRING, primaryKey: true},
space_id: Sequelize.STRING,
user_id: Sequelize.STRING,
role: Sequelize.STRING,
code: Sequelize.STRING,
state: {type: Sequelize.STRING, defaultValue: "pending"},
created_at: {type: Sequelize.DATE, defaultValue: Sequelize.NOW},
updated_at: {type: Sequelize.DATE, defaultValue: Sequelize.NOW}
}),
Message: sequelize.define('message', {
_id: {type: Sequelize.STRING, primaryKey: true},
space_id: Sequelize.STRING,
user_id: Sequelize.STRING,
editor_name: Sequelize.STRING,
message: Sequelize.TEXT,
created_at: {type: Sequelize.DATE, defaultValue: Sequelize.NOW},
updated_at: {type: Sequelize.DATE, defaultValue: Sequelize.NOW}
}),
Artifact: sequelize.define('artifact', {
_id: {type: Sequelize.STRING, primaryKey: true},
space_id: Sequelize.STRING,
user_id: Sequelize.STRING,
mime: Sequelize.STRING,
thumbnail_uri: Sequelize.STRING,
last_update_user_id: Sequelize.STRING,
editor_name: Sequelize.STRING,
last_update_editor_name: Sequelize.STRING,
description: Sequelize.TEXT,
state: {type: Sequelize.STRING, default: "idle"},
//linked_to: Sequelize.STRING,
title: Sequelize.STRING,
tags: Sequelize.TEXT,
search_text: Sequelize.STRING,
link_uri: Sequelize.STRING,
play_from: Sequelize.DECIMAL,
play_to: Sequelize.DECIMAL,
x: {type: Sequelize.DECIMAL, default: 0.0},
y: {type: Sequelize.DECIMAL, default: 0.0},
z: {type: Sequelize.DECIMAL, default: 0.0},
r: {type: Sequelize.DECIMAL, default: 0.0},
w: {type: Sequelize.DECIMAL, default: 100},
h: {type: Sequelize.DECIMAL, default: 100},
//control_points: [{
// dx: Number, dy: Number
//}],
control_points: Sequelize.TEXT,
group: Sequelize.STRING,
locked: {type: Sequelize.BOOLEAN, default: false},
payload_uri: Sequelize.STRING,
payload_thumbnail_web_uri: Sequelize.STRING,
payload_thumbnail_medium_uri: Sequelize.STRING,
payload_thumbnail_big_uri: Sequelize.STRING,
payload_size: Sequelize.INTEGER, // file size in bytes
fill_color: {type: Sequelize.STRING, default: "transparent"},
stroke_color: {type: Sequelize.STRING, default: "#000000"},
text_color: Sequelize.STRING,
stroke: {type: Sequelize.DECIMAL, default: 0.0},
stroke_style: {type: Sequelize.STRING, default: "solid"},
alpha: {type: Sequelize.DECIMAL, default: 1.0},
order: {type: Sequelize.INTEGER, default: 0},
crop_x: Sequelize.INTEGER,
crop_y: Sequelize.INTEGER,
crop_w: Sequelize.INTEGER,
crop_h: Sequelize.INTEGER,
shape: Sequelize.STRING,
shape_svg: Sequelize.STRING,
padding_left: Sequelize.INTEGER,
padding_right: Sequelize.INTEGER,
padding_top: Sequelize.INTEGER,
padding_bottom: Sequelize.INTEGER,
margin_left: Sequelize.INTEGER,
margin_right: Sequelize.INTEGER,
margin_top: Sequelize.INTEGER,
margin_bottom: Sequelize.INTEGER,
border_radius: Sequelize.INTEGER,
align: {type: Sequelize.STRING, default: "left"},
valign: {type: Sequelize.STRING, default: "top"},
brightness: Sequelize.DECIMAL,
contrast: Sequelize.DECIMAL,
saturation: Sequelize.DECIMAL,
blur: Sequelize.DECIMAL,
hue: Sequelize.DECIMAL,
opacity: Sequelize.DECIMAL,
payload_alternatives: Sequelize.TEXT,
/*payload_alternatives: [{
mime: String,
payload_uri: String,
payload_thumbnail_web_uri: String,
payload_thumbnail_medium_uri: String,
payload_thumbnail_big_uri: String,
payload_size: Number
}],*/
created_at: {type: Sequelize.DATE, defaultValue: Sequelize.NOW},
updated_at: {type: Sequelize.DATE, defaultValue: Sequelize.NOW}
}),
init: function() {
User = this.User;
Session = this.Session;
Space = this.Space;
Artifact = this.Artifact;
Message = this.Message;
Membership = this.Membership;
Space.belongsTo(User, {
foreignKey: {
name: 'creator_id'
},
as: 'creator'
});
Membership.belongsTo(User, {
foreignKey: {
name: 'user_id'
},
as: 'user'
});
Membership.belongsTo(Space, {
foreignKey: {
name: 'space_id'
},
as: 'space'
});
Artifact.belongsTo(User, {
foreignKey: {
name: 'user_id'
},
as: 'user'
});
Artifact.belongsTo(Space, {
foreignKey: {
name: 'space_id'
},
as: 'space'
});
Message.belongsTo(User, {
foreignKey: {
name: 'user_id'
},
as: 'user'
});
Message.belongsTo(Space, {
foreignKey: {
name: 'space_id'
},
as: 'space'
});
sequelize.sync();
},
getUserRoleInSpace: (originalSpace, user, cb) => {
originalSpace.path = [];
console.log("getUserRoleInSpace",originalSpace._id,user._id,user.home_folder_id);
if (originalSpace._id == user.home_folder_id || (originalSpace.creator_id && originalSpace.creator_id == user._id)) {
cb("admin");
} else {
var findMembershipsForSpace = function(space, allMemberships, prevRole) {
Membership.findAll({ where: {
"space": space._id
}}).then(function(parentMemberships) {
var currentMemberships = parentMemberships.concat(allMemberships);
if (space.parent_space_id) {
Space.findOne({ where: {
"_id": space.parent_space_id
}}, function(err, parentSpace) {
findMembershipsForSpace(parentSpace, currentMemberships, prevRole);
});
} else {
// reached the top
var role = prevRole;
space.memberships = currentMemberships;
if (role == "none") {
if (originalSpace.access_mode == "public") {
role = "viewer";
}
}
currentMemberships.forEach(function(m, i) {
if (m.user_id && m.user_id == user._id) {
role = m.role;
}
});
cb(role);
}
});
};
findMembershipsForSpace(originalSpace, [], "none");
}
},
spaceToObject: (space) => {
// FIXME TODO
return space;
},
findUserBySessionToken: (token, cb) => {
Session.findOne({where: {token: token}})
.then(session => {
if (!session) cb(null, null)
else User.findOne({where: {_id: session.user_id}})
.then(user => {
cb(null, user)
})
})
},
unpackArtifact: (a) => {
if (a.tags) {
a.tags = JSON.parse(a.tags);
}
if (a.control_points) {
a.control_points = JSON.parse(a.control_points);
}
if (a.payload_alternatives) {
a.payload_alternatives = JSON.parse(a.payload_alternatives);
}
return a;
},
packArtifact: (a) => {
if (a.tags) {
a.tags = JSON.stringify(a.tags);
}
if (a.control_points) {
a.control_points = JSON.stringify(a.control_points);
}
if (a.payload_alternatives) {
a.payload_alternatives = JSON.stringify(a.payload_alternatives);
}
return a;
}
}

View File

@ -1,21 +0,0 @@
'use strict';
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
module.exports.domainSchema = mongoose.Schema({
domain: String,
edu: Boolean,
created_at: {
type: Date,
default: Date.now
},
updated_at: {
type: Date,
default: Date.now
}
});
module.exports.domainSchema.index({
domain: 1
});

View File

@ -1,45 +0,0 @@
'use strict';
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
module.exports.membershipSchema = mongoose.Schema({
user: {
type: Schema.Types.ObjectId,
ref: 'User'
},
space: {
type: Schema.Types.ObjectId,
ref: 'Space'
},
team: {
type: Schema.Types.ObjectId,
ref: 'Team'
},
role: {
type: String,
default: "viewer"
},
state: {
type: String,
default: "active"
},
email_invited: String,
code: String,
created_at: {
type: Date,
default: Date.now
},
updated_at: {
type: Date,
default: Date.now
}
});
module.exports.membershipSchema.index({
user: 1,
space: 1,
team: 1,
code: 1
});

View File

@ -1,31 +0,0 @@
'use strict';
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
module.exports.messageSchema = mongoose.Schema({
user: {
type: Schema.Types.ObjectId,
ref: 'User'
},
editor_name: String,
space: {
type: Schema.Types.ObjectId,
ref: 'Space'
},
message: String,
created_from_ip: {type: String},
created_at: {
type: Date,
default: Date.now
},
updated_at: {
type: Date,
default: Date.now
}
});
module.exports.messageSchema.index({
space: 1,
user: 1
});

View File

@ -1,44 +0,0 @@
'use strict';
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
Plan = mongoose.model('Plan', {
key: String,
description: String,
limit_folders: {
type: Number,
default: 200
},
limit_spaces: {
type: Number,
default: 500
},
limit_storage_bytes: {
type: Number,
default: 10737418240
},
plan_type: {
type: String,
default: "org"
},
price: Number,
public: Boolean,
recurring: {
type: String,
default: "month"
},
title: String,
trial_days: Number,
voucher_code: String,
created_at: {
type: Date,
default: Date.now
},
updated_at: {
type: Date,
default: Date.now
}
});
exports.planModel = Plan;

View File

@ -1,12 +0,0 @@
//'use strict';
var mongoose = require('mongoose');
User = mongoose.model('User', require('./user').userSchema);
Action = mongoose.model('Action', require('./action').actionSchema);
Space = mongoose.model('Space', require('./space').spaceSchema);
Artifact = mongoose.model('Artifact', require('./artifact').artifactSchema);
Team = mongoose.model('Team', require('./team').teamSchema);
Message = mongoose.model('Message', require('./message').messageSchema);
Membership = mongoose.model('Membership', require('./membership').membershipSchema);
Domain = mongoose.model('Domain', require('./domain').domainSchema);

View File

@ -1,273 +0,0 @@
'use strict';
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var async = require('async');
var _ = require("underscore");
var crypto = require('crypto');
module.exports.spaceSchema = Schema({
name: {type: String, default: "New Space"},
space_type: {type: String, default: "space"},
creator : { type: Schema.Types.ObjectId, ref: 'User' },
parent_space_id: Schema.Types.ObjectId,
access_mode: {type: String, default: "private"}, // "public" || "private"
password: String,
edit_hash: String,
edit_slug: String,
editors_locking: Boolean,
thumbnail_uri: String,
stats: {
num_children: Number,
total_spaces: Number,
total_folders: Number,
storage_bytes: Number,
},
advanced: {
type: {
width: Number,
height: Number,
margin: Number,
background_color: String,
background_uri: String,
background_repeat: Boolean,
grid_size: Number,
grid_divisions: Number,
gutter: Number,
columns: Number,
column_max_width: Number,
columns_responsive: Number,
row_max_height: Number,
padding_horz: Number,
padding_vert: Number
},
default: {
width: 200,
height: 400,
margin: 0,
background_color: "rgba(255,255,255,1)"
}
},
blocked_at: {type: Date, default: Date.now},
created_at: {type: Date, default: Date.now},
updated_at: {type: Date, default: Date.now},
thumbnail_updated_at: {type: Date},
thumbnail_url: String
});
module.exports.spaceSchema.index({ creator: 1, parent_space_id: 1, created_at: 1, updated_at: 1, edit_hash: 1});
module.exports.spaceSchema.statics.allForUser = function (user, callback) {
return this.find({user_id: user_id}, callback);
};
module.exports.spaceSchema.statics.getMemberships = function (err, callback) {
callback(null, {});
};
var getRecursiveSubspacesForSpace = (parentSpace, cb) => {
if (parentSpace.space_type == "folder") {
Space.find({
"parent_space_id": parentSpace._id
}).exec((err, subspaces) => {
async.map(subspaces, (space, innerCb) => {
getRecursiveSubspacesForSpace(space, (err, spaces) => {
innerCb(err, spaces);
});
}, (err, subspaces) => {
var flattenSubspaces = _.flatten(subspaces);
flattenSubspaces.push(parentSpace);
cb(null, flattenSubspaces);
});
});
} else {
cb(null, [parentSpace]);
}
};
module.exports.spaceSchema.statics.getRecursiveSubspacesForSpace = getRecursiveSubspacesForSpace;
var roleMapping = {
"none": 0,
"viewer": 1,
"editor": 2,
"admin": 3
}
module.exports.spaceSchema.statics.roleInSpace = (originalSpace, user, cb) => {
if (user.home_folder_id.toString() === originalSpace._id.toString()) {
cb(null, "admin");
return;
}
if (originalSpace.creator) {
if (originalSpace.creator._id.toString() === user._id.toString()) {
cb(null, "admin");
return;
}
}
var findMembershipsForSpace = function(space, allMemberships, prevRole) {
Membership.find({
"space": space._id
}, (err, parentMemberships) => {
var currentMemberships = parentMemberships.concat(allMemberships);
if (space.parent_space_id) {
Space.findOne({
"_id": space.parent_space_id
}, function(err, parentSpace) {
var role = prevRole;
if(role == "none"){
if(originalSpace.access_mode == "public") {
role = "viewer";
}
}
findMembershipsForSpace(parentSpace, currentMemberships, role);
});
} else {
// reached the top
var role = prevRole;
space.memberships = currentMemberships;
currentMemberships.forEach(function(m, i) {
if (m.user && m.user.equals(user._id)) {
if (m.role != null) {
if (roleMapping[m.role] > roleMapping[role]) {
role = m.role;
}
}
}
});
cb(err, role);
}
});
};
findMembershipsForSpace(originalSpace, [], "none");
}
module.exports.spaceSchema.statics.recursiveDelete = (space, cb) => {
space.remove(function(err) {
Action.remove({
space: space
}, function(err) {
if (err)
console.error("removed actions for space: ", err);
});
Membership.remove({
space: space
}, function(err) {
if (err)
console.error("removed memberships for space: ", err);
});
if (space.space_type === "folder") {
Space
.find({
parent_space_id: space._id
})
.exec(function(err, spaces) {
async.eachLimit(spaces, 10, function(subSpace, innerCb) {
module.exports.spaceSchema.statics.recursiveDelete(subSpace, function(err) {
innerCb(err);
});
}, function(err) {
cb(err);
});
});
} else {
Artifact.find({
space_id: space._id
}, function(err, artifacts) {
if (err) cb(err);
else {
async.eachLimit(artifacts, 20, function(a, innerCb) {
a.remove(function(err) {
innerCb(null, a);
});
}, function(err) {
cb(err);
});
}
});
}
});
};
var duplicateRecursiveSpace = (space, user, depth, cb, newParentSpace) => {
var newSpace = new Space(space);
newSpace._id = mongoose.Types.ObjectId();
if (newParentSpace) {
newSpace.parent_space_id = newParentSpace._id;
} else {
newSpace.name = newSpace.name + " (b)";
}
newSpace.creator = user;
newSpace.created_at = new Date();
newSpace.updated_at = new Date();
if (newSpace.space_type === "space") {
newSpace.edit_hash = crypto.randomBytes(64).toString('hex').substring(0, 7);
}
newSpace.save(function(err) {
if (newSpace.space_type === "folder" && depth < 10) {
Space
.find({
parent_space_id: space._id
})
.exec(function(err, spaces) {
async.eachLimit(spaces, 10, function(subSpace, innerCb) {
duplicateRecursiveSpace(subSpace, user, ++depth, function(err, newSubSpace) {
innerCb(err, newSubSpace);
}, newSpace);
}, function(err, allNewSubspaces) {
cb(err, newSpace);
});
});
} else {
Artifact.find({
space_id: space._id
}, function(err, artifacts) {
if (err) innerCb(err);
else {
async.eachLimit(artifacts, 20, function(a, innerCb) {
var newArtifact = new Artifact(a);
newArtifact._id = mongoose.Types.ObjectId();
newArtifact.space_id = newSpace._id;
newArtifact.created_at = new Date();
newArtifact.updated_at = new Date();
newArtifact.save(function(err) {
innerCb(null, newArtifact);
});
}, function(err, allNewArtifacts) {
cb(err, newSpace);
});
}
});
}
});
};
module.exports.spaceSchema.statics.duplicateSpace = duplicateRecursiveSpace;

View File

@ -1,70 +0,0 @@
'use strict';
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
module.exports.teamSchema = mongoose.Schema({
name: String,
subdomain: String,
creator: {
type: Schema.Types.ObjectId,
ref: 'User'
},
admins: [{
type: Schema.Types.ObjectId,
ref: 'User'
}],
invitation_codes: [String],
avatar_thumb_uri: String,
avatar_uri: String,
payment_type: {
type: String,
default: "auto"
},
payment_plan_key: String,
payment_subscription_id: String,
blocked_at: {
type: Date
},
upgraded_at: {
type: Date
},
created_at: {
type: Date,
default: Date.now
},
updated_at: {
type: Date,
default: Date.now
}
});
module.exports.teamSchema.index({
creator: 1
});
module.exports.teamSchema.statics.getTeamForHost = (host, cb) => {
if (host != "127.0.0.1:9666") { //phantomjs check
let subDomainParts = host.split('.');
if (subDomainParts.length > 2) {
const subdomain = subDomainParts[0];
if (subdomain != "www") {
Team.findOne({
subdomain: subdomain
}).exec((err, team) => {
cb(err, team, subdomain)
});
} else {
cb(null, null)
}
} else {
cb(null, null);
}
} else {
cb(null, null);
}
}

View File

@ -1,53 +0,0 @@
'use strict';
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
module.exports.userSchema = mongoose.Schema({
email: String,
password_hash: String,
nickname: String,
account_type: {type: String, default: "email"},
created_at: {type: Date, default: Date.now},
updated_at: {type: Date, default: Date.now},
avatar_original_uri: String,
avatar_thumb_uri: String,
src: String,
confirmation_token: String,
confirmed_at: Date,
password_reset_token: String,
home_folder_id: Schema.Types.ObjectId,
team : { type: Schema.Types.ObjectId, ref: 'Team' },
preferences: {
language: String,
email_notifications: {type: Boolean, default: true},
daily_digest_last_send: Date,
daily_digest: {type: Boolean, default: true}
},
sessions: [
{
token: String,
expires: Date,
device: String,
ip: String,
created_at: Date
}
],
payment_info: String,
payment_plan_key: {type: String, default: "free"},
payment_customer_id: String,
payment_subscription_id: String,
payment_notification_state: Number
});
module.exports.userSchema.index({
email: 1,
"sessions.token": 1,
team: 1,
created_at: 1,
home_folder_id: 1
});
module.exports.userSchema.statics.findBySessionToken = function (token, cb) {
return this.findOne({ "sessions.token": token}, cb);
};

View File

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

View File

@ -133,6 +133,14 @@ function load_spaces(id, is_home, on_success, on_error) {
}, on_error); }, on_error);
} }
function load_importables(user, on_success, on_error) {
load_resource("get", "/users/"+user._id+"/importables", null, on_success, on_error);
}
function import_zip(user, filename, on_success, on_error) {
load_resource("get", "/users/"+user._id+"/import?zip="+filename, null, on_success, on_error);
}
function load_writable_folders(on_success, on_error) { function load_writable_folders(on_success, on_error) {
load_resource("get", "/spaces?writablefolders=true", null, on_success, on_error); load_resource("get", "/spaces?writablefolders=true", null, on_success, on_error);
} }

View File

@ -8,23 +8,28 @@ SpacedeckAccount = {
account_confirmed_sent: false, account_confirmed_sent: false,
account_tab: 'invoices', account_tab: 'invoices',
password_change_error: null, password_change_error: null,
feedback_text: "" feedback_text: "",
importables: [], // spacedeck.com zip import files
}, },
methods: { methods: {
show_account: function(user) { show_account: function() {
this.activate_dropdown('account'); this.activate_dropdown('account');
this.load_subscription(); },
this.load_billing();
start_zip_import: function(f) {
if (confirm("Your archive will be imported in the background. This can take a few minutes. You can continue using Spacedeck in the meantime.")) {
import_zip(this.user, f);
}
}, },
account_save_user_digest: function(val) { account_save_user_digest: function(val) {
this.user.preferences.daily_digest = val; this.user.prefs_email_digest = val;
this.save_user(function() { this.save_user(function() {
}); });
}, },
account_save_user_notifications: function(val) { account_save_user_notifications: function(val) {
this.user.preferences.email_notifications = val; this.user.prefs_email_notifications = val;
this.save_user(function() { this.save_user(function() {
}); });
}, },
@ -36,13 +41,11 @@ SpacedeckAccount = {
save_user_language: function(lang) { save_user_language: function(lang) {
localStorage.lang = lang; localStorage.lang = lang;
if (this.user.preferences) { this.user.prefs_language = lang;
this.user.preferences.language = lang;
this.save_user(function() { this.save_user(function() {
window._spacedeck_location_change = true; window._spacedeck_location_change = true;
location.href="/spaces"; location.href="/spaces";
}.bind(this)); }.bind(this));
}
}, },
save_user: function(on_success) { save_user: function(on_success) {

View File

@ -61,16 +61,16 @@ var SpacedeckBoardArtifacts = {
}, },
artifact_link: function(a) { artifact_link: function(a) {
if (a.meta && a.meta.link_uri) { if (a.link_uri) {
return a.meta.link_uri; return a.link_uri;
} else { } else {
return ""; return "";
} }
}, },
artifact_link_caption: function(a) { artifact_link_caption: function(a) {
if (a.meta && a.meta.link_uri) { if (a.link_uri) {
var parts = a.meta.link_uri.split("/"); var parts = a.link_uri.split("/");
// scheme://domain.foo/... // scheme://domain.foo/...
// 0 1 2 // 0 1 2
if (parts.length>2) { if (parts.length>2) {
@ -102,10 +102,8 @@ var SpacedeckBoardArtifacts = {
if (this.artifact_is_selected(a) && this.editing_artifact_id!=a._id) clzs.push("selected"); if (this.artifact_is_selected(a) && this.editing_artifact_id!=a._id) clzs.push("selected");
if (!a._id) clzs.push("creating"); if (!a._id) clzs.push("creating");
if (a.style) { if (a.align) clzs.push("align-"+a.align);
clzs.push("align-"+a.style.align); if (a.valign) clzs.push("align-"+a.valign);
clzs.push("align-"+a.style.valign);
}
clzs.push("state-"+a.state); clzs.push("state-"+a.state);
@ -123,56 +121,56 @@ var SpacedeckBoardArtifacts = {
artifact_inner_style: function(a) { artifact_inner_style: function(a) {
var styles = []; var styles = [];
if (a.style) { //if (a.style) {
var svg_style = ((a.mime.match("vector") || a.mime.match("shape")) && a.style.shape!="square"); var svg_style = ((a.mime.match("vector") || a.mime.match("shape")) && a.shape!="square");
if (!svg_style) { if (!svg_style) {
if (a.style.stroke) { if (a.stroke) {
styles.push("border-width:"+a.style.stroke+"px"); styles.push("border-width:"+a.stroke+"px");
styles.push("border-style:"+(a.style.stroke_style||"solid")); styles.push("border-style:"+(a.stroke_style||"solid"));
} }
if (a.style.stroke_color) { if (a.stroke_color) {
styles.push("border-color:"+a.style.stroke_color); styles.push("border-color:"+a.stroke_color);
} }
if (a.style.border_radius) { if (a.border_radius) {
styles.push("border-radius:"+a.style.border_radius+"px"); styles.push("border-radius:"+a.border_radius+"px");
} }
} }
if (a.style.fill_color && !svg_style) { if (a.fill_color && !svg_style) {
styles.push("background-color:"+a.style.fill_color); styles.push("background-color:"+a.fill_color);
} }
if (a.style.text_color) { if (a.text_color) {
styles.push("color:"+a.style.text_color); styles.push("color:"+a.text_color);
} }
var filters = []; var filters = [];
if (!isNaN(a.style.brightness) && a.style.brightness != 100) { if (!isNaN(a.brightness) && a.brightness != 100) {
filters.push("brightness("+a.style.brightness+"%)"); filters.push("brightness("+a.brightness+"%)");
} }
if (!isNaN(a.style.contrast) && a.style.contrast != 100) { if (!isNaN(a.contrast) && a.contrast != 100) {
filters.push("contrast("+a.style.contrast+"%)"); filters.push("contrast("+a.contrast+"%)");
} }
if (!isNaN(a.style.opacity) && a.style.opacity != 100) { if (!isNaN(a.opacity) && a.opacity != 100) {
filters.push("opacity("+a.style.opacity+"%)"); filters.push("opacity("+a.opacity+"%)");
} }
if (!isNaN(a.style.hue) && a.style.hue) { if (!isNaN(a.hue) && a.hue) {
filters.push("hue-rotate("+a.style.hue+"deg)"); filters.push("hue-rotate("+a.hue+"deg)");
} }
if (!isNaN(a.style.saturation) && a.style.saturation != 100) { if (!isNaN(a.saturation) && a.saturation != 100) {
filters.push("saturate("+a.style.saturation+"%)"); filters.push("saturate("+a.saturation+"%)");
} }
if (!isNaN(a.style.blur) && a.style.blur) { if (!isNaN(a.blur) && a.blur) {
filters.push("blur("+a.style.blur+"px)"); filters.push("blur("+a.blur+"px)");
} }
if (filters.length) { if (filters.length) {
styles.push("-webkit-filter:"+filters.join(" ")); styles.push("-webkit-filter:"+filters.join(" "));
styles.push("filter:"+filters.join(" ")); styles.push("filter:"+filters.join(" "));
} }
} //}
return styles.join(";"); return styles.join(";");
}, },
@ -180,12 +178,10 @@ var SpacedeckBoardArtifacts = {
artifact_text_cell_style: function(a, for_text_editor) { artifact_text_cell_style: function(a, for_text_editor) {
var styles = []; var styles = [];
if (a.style) { if (a.padding_left) styles.push("padding-left:"+a.padding_left+"px");
if (a.style.padding_left) styles.push("padding-left:"+a.style.padding_left+"px"); if (a.padding_right) styles.push("padding-right:"+a.padding_right+"px");
if (a.style.padding_right) styles.push("padding-right:"+a.style.padding_right+"px"); if (a.padding_top) styles.push("padding-top:"+a.padding_top+"px");
if (a.style.padding_top) styles.push("padding-top:"+a.style.padding_top+"px"); if (a.padding_bottom) styles.push("padding-bottom:"+a.padding_bottom+"px");
if (a.style.padding_bottom) styles.push("padding-bottom:"+a.style.padding_bottom+"px");
}
return styles.join(";"); return styles.join(";");
}, },
@ -194,25 +190,21 @@ var SpacedeckBoardArtifacts = {
var styles = []; var styles = [];
var z = 0; var z = 0;
if (a.board) { z = a.z;
z = a.board.z;
if (z<0) z=0; // fix negative z-index if (z<0) z=0; // fix negative z-index
styles = [ styles = [
"left:" +a.board.x+"px", "left:" +a.x+"px",
"top:" +a.board.y+"px", "top:" +a.y+"px",
"width:" +a.board.w+"px", "width:" +a.w+"px",
"height:"+a.board.h+"px", "height:"+a.h+"px",
"z-index:"+z "z-index:"+z
]; ];
}
if (a.style) { if (a.margin_left) styles.push("margin-left:"+a.margin_left+"px");
if (a.style.margin_left) styles.push("margin-left:"+a.style.margin_left+"px"); if (a.margin_right) styles.push("margin-right:"+a.margin_right+"px");
if (a.style.margin_right) styles.push("margin-right:"+a.style.margin_right+"px"); if (a.margin_top) styles.push("margin-top:"+a.margin_top+"px");
if (a.style.margin_top) styles.push("margin-top:"+a.style.margin_top+"px"); if (a.margin_bottom) styles.push("margin-bottom:"+a.margin_bottom+"px");
if (a.style.margin_bottom) styles.push("margin-bottom:"+a.style.margin_bottom+"px");
}
// FIXME: via class logic? // FIXME: via class logic?
if (a.mime.match("vector")) { if (a.mime.match("vector")) {
@ -241,7 +233,7 @@ var SpacedeckBoardArtifacts = {
artifact_thumbnail_uri: function(a) { artifact_thumbnail_uri: function(a) {
if (a.payload_thumbnail_big_uri && a.board) { if (a.payload_thumbnail_big_uri && a.board) {
if (a.board.w>800) { if (a.w>800) {
return a.payload_thumbnail_big_uri; return a.payload_thumbnail_big_uri;
} }
} }
@ -255,35 +247,35 @@ var SpacedeckBoardArtifacts = {
var type = parts[0]; var type = parts[0];
var provider = parts[1]; var provider = parts[1];
if (!a.meta || !a.meta.link_uri) { if (!a.link_uri) {
console.log("missing meta / link_uri: ",a); console.log("missing meta / link_uri: ",a);
console.log("type/provider: ",type,provider); console.log("type/provider: ",type,provider);
return ("missing metadata: "+a._id); return ("missing metadata: "+a._id);
} }
if (provider=="youtube") { if (provider=="youtube") {
var vid = a.meta.link_uri.match(/(v=|\/)([a-zA-Z0-9\-_]{11})/); var vid = a.link_uri.match(/(v=|\/)([a-zA-Z0-9\-_]{11})/);
if (vid && vid.length>2) { if (vid && vid.length>2) {
var uri = "https://youtube.com/embed/"+vid[2]; var uri = "https://youtube.com/embed/"+vid[2];
return "<iframe frameborder=0 allowfullscreen src=\""+uri+"?showinfo=0&rel=0&controls=0\"></iframe>"; return "<iframe frameborder=0 allowfullscreen src=\""+uri+"?showinfo=0&rel=0&controls=0\"></iframe>";
} else return "Can't resolve: "+a.payload_uri; } else return "Can't resolve: "+a.payload_uri;
} else if (provider=="dailymotion") { } else if (provider=="dailymotion") {
var match = a.meta.link_uri.match(/dailymotion.com\/video\/([^<]*)/); var match = a.link_uri.match(/dailymotion.com\/video\/([^<]*)/);
if (match && match.length>1) { if (match && match.length>1) {
var uri = "https://www.dailymotion.com/embed/video/"+match[1]; var uri = "https://www.dailymotion.com/embed/video/"+match[1];
return "<iframe frameborder=0 allowfullscreen src=\""+uri+"\"></iframe>"; return "<iframe frameborder=0 allowfullscreen src=\""+uri+"\"></iframe>";
} else return "Can't resolve: "+a.payload_uri; } else return "Can't resolve: "+a.payload_uri;
} else if (provider=="vimeo") { } else if (provider=="vimeo") {
var match = a.meta.link_uri.match(/https?:\/\/(www\.)?vimeo.com\/(\d+)($|\/)/); var match = a.link_uri.match(/https?:\/\/(www\.)?vimeo.com\/(\d+)($|\/)/);
if (match) { if (match) {
var uri = "https://player.vimeo.com/video/"+match[2]; var uri = "https://player.vimeo.com/video/"+match[2];
return "<iframe frameborder=0 allowfullscreen src=\""+uri+"\"></iframe>"; return "<iframe frameborder=0 allowfullscreen src=\""+uri+"\"></iframe>";
} else return "Can't resolve: "+a.payload_uri; } else return "Can't resolve: "+a.payload_uri;
} else if (provider=="soundcloud") { } else if (provider=="soundcloud") {
return '<iframe width="100%" height="166" scrolling="no" frameborder="no" src="https://w.soundcloud.com/player/?url='+a.meta.link_uri.replace(":", "%3A")+'"></iframe>'; return '<iframe width="100%" height="166" scrolling="no" frameborder="no" src="https://w.soundcloud.com/player/?url='+a.link_uri.replace(":", "%3A")+'"></iframe>';
} else if (provider=="spacedeck") { } else if (provider=="spacedeck") {
@ -299,8 +291,8 @@ var SpacedeckBoardArtifacts = {
if (mtype != "vector" && mtype != "shape") return ""; if (mtype != "vector" && mtype != "shape") return "";
var shape = a.style.shape || ""; var shape = a.shape || "";
var padding = 32 + a.style.stroke*2; var padding = 32 + a.stroke*2;
var path_svg; var path_svg;
var fill = ""; var fill = "";
@ -310,13 +302,13 @@ var SpacedeckBoardArtifacts = {
fill = "fill:none"; fill = "fill:none";
} else { } else {
path_svg = render_vector_shape(a, padding); path_svg = render_vector_shape(a, padding);
fill = "fill:"+a.style.fill_color+";"; fill = "fill:"+a.fill_color+";";
padding = 0; padding = 0;
} }
var margin = padding; var margin = padding;
var svg = "<svg xmlns='http://www.w3.org/2000/svg' width='"+(a.board.w+2*padding)+"' height='"+(a.board.h+2*padding)+"' "; var svg = "<svg xmlns='http://www.w3.org/2000/svg' width='"+(a.w+2*padding)+"' height='"+(a.h+2*padding)+"' ";
svg += "style='margin-left:"+(-margin)+"px;margin-top:"+(-margin)+"px;stroke-width:"+a.style.stroke+";stroke:"+a.style.stroke_color+";"+fill+"'>"; svg += "style='margin-left:"+(-margin)+"px;margin-top:"+(-margin)+"px;stroke-width:"+a.stroke+";stroke:"+a.stroke_color+";"+fill+"'>";
svg += path_svg; svg += path_svg;
svg += "</svg>"; svg += "</svg>";
@ -329,10 +321,10 @@ var SpacedeckBoardArtifacts = {
if (arts.length==0) return null; if (arts.length==0) return null;
r = { r = {
x1: parseInt(_.min(arts.map(function(a){return a.board.x}))), x1: parseInt(_.min(arts.map(function(a){return a.x}))),
y1: parseInt(_.min(arts.map(function(a){return a.board.y}))), y1: parseInt(_.min(arts.map(function(a){return a.y}))),
x2: parseInt(_.max(arts.map(function(a){return a.board.x+a.board.w}))), x2: parseInt(_.max(arts.map(function(a){return a.x+a.w}))),
y2: parseInt(_.max(arts.map(function(a){return a.board.y+a.board.h}))) y2: parseInt(_.max(arts.map(function(a){return a.y+a.h})))
}; };
r.x=r.x1; r.x=r.x1;
r.y=r.y1; r.y=r.y1;
@ -356,7 +348,7 @@ var SpacedeckBoardArtifacts = {
artifacts_in_rect: function(rect) { artifacts_in_rect: function(rect) {
return _.filter(this.active_space_artifacts, function(a) { return _.filter(this.active_space_artifacts, function(a) {
return this.rects_intersecting(a.board, rect); return this.rects_intersecting(a, rect);
}.bind(this)); }.bind(this));
}, },
@ -366,15 +358,15 @@ var SpacedeckBoardArtifacts = {
var rect = this.artifact_selection_rect(); var rect = this.artifact_selection_rect();
var overlapping = _.filter(this.artifacts_in_rect(rect), function(a){return !this.is_selected(a)}.bind(this)); var overlapping = _.filter(this.artifacts_in_rect(rect), function(a){return !this.is_selected(a)}.bind(this));
var max_z = _.max(overlapping,function(a){ return a.board.z; }); var max_z = _.max(overlapping,function(a){ return a.z; });
if (max_z.board) { if (max_z.board) {
max_z = max_z.board.z + 1; max_z = max_z.z + 1;
} else { } else {
max_z = 1; max_z = 1;
} }
this.update_selected_artifacts(function(a) { this.update_selected_artifacts(function(a) {
return { board: _.extend(a.board, { z: max_z }) }; return { z: max_z };
}); });
}, },
@ -384,15 +376,15 @@ var SpacedeckBoardArtifacts = {
var rect = this.artifact_selection_rect(); var rect = this.artifact_selection_rect();
var overlapping = _.filter(this.artifacts_in_rect(rect), function(a){return !this.is_selected(a);}.bind(this)); var overlapping = _.filter(this.artifacts_in_rect(rect), function(a){return !this.is_selected(a);}.bind(this));
var min_z = _.min(overlapping,function(a){ return (a.board?a.board.z:0); }); var min_z = _.min(overlapping,function(a){ return a.z; });
if (min_z.board) { if (min_z.board) {
min_z = min_z.board.z - 1; min_z = min_z.z - 1;
} else { } else {
min_z = 0; min_z = 0;
} }
var my_z = _.max(this.selected_artifacts(),function(a){ (a.board?a.board.z:0); }); var my_z = _.max(this.selected_artifacts(),function(a){ return a.z; });
if (my_z.board) { if (my_z.board) {
my_z = my_z.board.z - 1; my_z = my_z.z - 1;
} else { } else {
my_z = 0; my_z = 0;
} }
@ -400,14 +392,14 @@ var SpacedeckBoardArtifacts = {
// TODO: move all other items up in this case? // TODO: move all other items up in this case?
if (min_z < 0) { if (min_z < 0) {
this.update_artifacts(overlapping, function(a) { this.update_artifacts(overlapping, function(a) {
return { board: _.extend(a.board, { z: (my_z + (a.board?a.board.z:0) + 1) }) }; return { z: (my_z + a.z + 1) };
}); });
return; return;
} }
this.update_selected_artifacts(function(a) { this.update_selected_artifacts(function(a) {
return { board: _.extend(a.board, { z: min_z }) }; return { z: min_z };
}); });
}, },
@ -416,7 +408,7 @@ var SpacedeckBoardArtifacts = {
var rect = this.artifact_selection_rect(); var rect = this.artifact_selection_rect();
this.update_selected_artifacts(function(a) { this.update_selected_artifacts(function(a) {
return { board: _.extend(a.board, { x: rect.x1 }) }; return { x: rect.x1 };
}); });
}, },
@ -425,7 +417,7 @@ var SpacedeckBoardArtifacts = {
var rect = this.artifact_selection_rect(); var rect = this.artifact_selection_rect();
this.update_selected_artifacts(function(a) { this.update_selected_artifacts(function(a) {
return { board: _.extend(a.board, { y: rect.y1 }) }; return { y: rect.y1 };
}); });
}, },
@ -434,7 +426,7 @@ var SpacedeckBoardArtifacts = {
var rect = this.artifact_selection_rect(); var rect = this.artifact_selection_rect();
this.update_selected_artifacts(function(a) { this.update_selected_artifacts(function(a) {
return { board: _.extend(a.board, { x: rect.x2 - a.board.w }) }; return { x: rect.x2 - a.w };
}); });
}, },
@ -443,7 +435,7 @@ var SpacedeckBoardArtifacts = {
var rect = this.artifact_selection_rect(); var rect = this.artifact_selection_rect();
this.update_selected_artifacts(function(a) { this.update_selected_artifacts(function(a) {
return { board: _.extend(a.board, { y: rect.y2 - a.board.h }) }; return { y: rect.y2 - a.h };
}); });
}, },
@ -453,7 +445,7 @@ var SpacedeckBoardArtifacts = {
var rect = this.artifact_selection_rect(); var rect = this.artifact_selection_rect();
var cx = rect.x1 + (rect.x2-rect.x1)/2; var cx = rect.x1 + (rect.x2-rect.x1)/2;
this.update_selected_artifacts(function(a) { this.update_selected_artifacts(function(a) {
return { board: _.extend(a.board, { x: cx - a.board.w/2 }) }; return { x: cx - a.w/2 };
}); });
}, },
@ -463,7 +455,7 @@ var SpacedeckBoardArtifacts = {
var rect = this.artifact_selection_rect(); var rect = this.artifact_selection_rect();
var cy = rect.y1 + (rect.y2-rect.y1)/2; var cy = rect.y1 + (rect.y2-rect.y1)/2;
this.update_selected_artifacts(function(a) { this.update_selected_artifacts(function(a) {
return { board: _.extend(a.board, { y: cy - a.board.h/2 }) }; return { y: cy - a.h/2 };
}); });
}, },
@ -473,11 +465,11 @@ var SpacedeckBoardArtifacts = {
var arts = this.selected_artifacts(); var arts = this.selected_artifacts();
if (arts.length<2) return; if (arts.length<2) return;
var totalw = _.reduce(arts, function(sum, a) { return sum + a.board.w }, 0); var totalw = _.reduce(arts, function(sum, a) { return sum + a.w }, 0);
var avgw = totalw / arts.length; var avgw = totalw / arts.length;
this.update_selected_artifacts(function(a) { this.update_selected_artifacts(function(a) {
return { board: _.extend(a.board, { w: avgw }) }; return { w: avgw };
}); });
}, },
@ -487,11 +479,11 @@ var SpacedeckBoardArtifacts = {
var arts = this.selected_artifacts(); var arts = this.selected_artifacts();
if (arts.length<2) return; if (arts.length<2) return;
var totalh = _.reduce(arts, function(sum, a) { return sum + a.board.h }, 0); var totalh = _.reduce(arts, function(sum, a) { return sum + a.h }, 0);
var avgh = totalh / arts.length; var avgh = totalh / arts.length;
this.update_selected_artifacts(function(a) { this.update_selected_artifacts(function(a) {
return { board: _.extend(a.board, { h: avgh }) }; return { h: avgh };
}); });
}, },
@ -506,16 +498,16 @@ var SpacedeckBoardArtifacts = {
var selected = this.selected_artifacts(); var selected = this.selected_artifacts();
if (selected.length<3) return; if (selected.length<3) return;
var sorted = _.sortBy(selected, function(a) { return a.board.x }); var sorted = _.sortBy(selected, function(a) { return a.x });
var startx = sorted[0].board.x + sorted[0].board.w/2; var startx = sorted[0].x + sorted[0].w/2;
var stopx = _.last(sorted).board.x + _.last(sorted).board.w/2; var stopx = _.last(sorted).x + _.last(sorted).w/2;
var step = (stopx-startx)/(sorted.length-1); var step = (stopx-startx)/(sorted.length-1);
for (var i=1; i<sorted.length-1; i++) { for (var i=1; i<sorted.length-1; i++) {
var a = sorted[i]; var a = sorted[i];
var x = startx + step*i - a.board.w/2; var x = startx + step*i - a.w/2;
this.update_artifacts([a],function(a) { this.update_artifacts([a],function(a) {
return { board: _.extend(a.board, {x: x}) } return { x: x }
}); });
} }
}, },
@ -526,16 +518,16 @@ var SpacedeckBoardArtifacts = {
var selected = this.selected_artifacts(); var selected = this.selected_artifacts();
if (selected.length<3) return; if (selected.length<3) return;
var sorted = _.sortBy(selected, function(a) { return a.board.y }); var sorted = _.sortBy(selected, function(a) { return a.y });
var starty = sorted[0].board.y + sorted[0].board.h/2; var starty = sorted[0].y + sorted[0].h/2;
var stopy = _.last(sorted).board.y + _.last(sorted).board.h/2; var stopy = _.last(sorted).y + _.last(sorted).h/2;
var step = (stopy-starty)/(sorted.length-1); var step = (stopy-starty)/(sorted.length-1);
for (var i=1; i<sorted.length-1; i++) { for (var i=1; i<sorted.length-1; i++) {
var a = sorted[i]; var a = sorted[i];
var y = starty + step*i - a.board.h/2; var y = starty + step*i - a.h/2;
this.update_artifacts([a],function(a) { this.update_artifacts([a],function(a) {
return { board: _.extend(a.board, {y: y}) } return { y: y }
}); });
} }
}, },
@ -546,21 +538,21 @@ var SpacedeckBoardArtifacts = {
var selected = this.selected_artifacts(); var selected = this.selected_artifacts();
if (selected.length<3) return; if (selected.length<3) return;
var sorted = _.sortBy(selected, function(a) { return a.board.x }); var sorted = _.sortBy(selected, function(a) { return a.x });
var startx = sorted[0].board.x; var startx = sorted[0].x;
var stopx = _.last(sorted).board.x + _.last(sorted).board.w; var stopx = _.last(sorted).x + _.last(sorted).w;
var range = stopx - startx; var range = stopx - startx;
var totalw = _.reduce(sorted, function(sum, a) { return sum + a.board.w }, 0); var totalw = _.reduce(sorted, function(sum, a) { return sum + a.w }, 0);
var avgs = (range - totalw) / (sorted.length-1); var avgs = (range - totalw) / (sorted.length-1);
var prevend = startx + sorted[0].board.w; var prevend = startx + sorted[0].w;
for (var i=1; i<sorted.length-1; i++) { for (var i=1; i<sorted.length-1; i++) {
var a = sorted[i]; var a = sorted[i];
var x = prevend + avgs; var x = prevend + avgs;
this.update_artifacts([a],function(a) { this.update_artifacts([a],function(a) {
return { board: _.extend(a.board, {x: x}) } return { x: x }
}); });
prevend = x+a.board.w; prevend = x+a.w;
} }
}, },
@ -570,21 +562,21 @@ var SpacedeckBoardArtifacts = {
var selected = this.selected_artifacts(); var selected = this.selected_artifacts();
if (selected.length<3) return; if (selected.length<3) return;
var sorted = _.sortBy(selected, function(a) { return a.board.y }); var sorted = _.sortBy(selected, function(a) { return a.y });
var starty = sorted[0].board.y; var starty = sorted[0].y;
var stopy = _.last(sorted).board.y + _.last(sorted).board.h; var stopy = _.last(sorted).y + _.last(sorted).h;
var range = stopy - starty; var range = stopy - starty;
var totalh = _.reduce(sorted, function(sum, a) { return sum + a.board.h }, 0); var totalh = _.reduce(sorted, function(sum, a) { return sum + a.h }, 0);
var avgs = (range - totalh) / (sorted.length-1); var avgs = (range - totalh) / (sorted.length-1);
var prevend = starty + sorted[0].board.h; var prevend = starty + sorted[0].h;
for (var i=1; i<sorted.length-1; i++) { for (var i=1; i<sorted.length-1; i++) {
var a = sorted[i]; var a = sorted[i];
var y = prevend + avgs; var y = prevend + avgs;
this.update_artifacts([a],function(a) { this.update_artifacts([a],function(a) {
return { board: _.extend(a.board, {y: y}) } return { y: y }
}); });
prevend = y+a.board.h; prevend = y+a.h;
} }
}, },
@ -594,20 +586,20 @@ var SpacedeckBoardArtifacts = {
var selected = this.selected_artifacts(); var selected = this.selected_artifacts();
if (selected.length<2) return; if (selected.length<2) return;
var sorted = _.sortBy(selected, function(a) { return a.board.x+a.board.y*this.active_space.advanced.width }.bind(this)); var sorted = _.sortBy(selected, function(a) { return a.x+a.y*this.active_space.advanced.width }.bind(this));
var minx = sorted[0].board.x; var minx = sorted[0].x;
var miny = sorted[0].board.y; var miny = sorted[0].y;
var sorted = _.sortBy(selected, function(a) { return -Math.max(a.board.w,a.board.h) }.bind(this)); var sorted = _.sortBy(selected, function(a) { return -Math.max(a.w,a.h) }.bind(this));
var blocks = []; var blocks = [];
for (var i=0; i<sorted.length; i++) { for (var i=0; i<sorted.length; i++) {
var a = sorted[i]; var a = sorted[i];
blocks.push({ blocks.push({
w: a.board.w, w: a.w,
h: a.board.h, h: a.h,
a: a a: a
}); });
} }
@ -620,10 +612,10 @@ var SpacedeckBoardArtifacts = {
if (block.fit) { if (block.fit) {
var a = block.a; var a = block.a;
this.update_artifacts([a],function(a) { this.update_artifacts([a],function(a) {
return { board: _.extend(a.board, { return {
x: minx+block.fit.x, x: minx+block.fit.x,
y: miny+block.fit.y y: miny+block.fit.y
}) } }
}); });
} }
} }

View File

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

View File

@ -369,8 +369,8 @@ var SpacedeckSections = {
// canvas // canvas
this.$watch('active_style.background_color', function (value, mutation) { this.$watch('active_style.background_color', function (value, mutation) {
if (this.active_style.background_color != this.active_space.advanced.background_color) { if (this.active_style.background_color != this.active_space.background_color) {
this.$set("active_space.advanced.background_color",this.active_style.background_color); this.$set("active_space.background_color",this.active_style.background_color);
this.throttled_save_active_space(); this.throttled_save_active_space();
} }
@ -448,7 +448,7 @@ var SpacedeckSections = {
for (var i=0; i<props.length; i++) { for (var i=0; i<props.length; i++) {
var prop = props[i]; var prop = props[i];
this.active_style[prop]=a.style[prop]; this.active_style[prop]=a[prop];
} }
// defaults // defaults
@ -457,10 +457,10 @@ var SpacedeckSections = {
this.active_style.line_height = this.default_style.line_height; this.active_style.line_height = this.default_style.line_height;
this.active_style.letter_spacing = this.default_style.letter_spacing; this.active_style.letter_spacing = this.default_style.letter_spacing;
this.active_style.padding_top = a.style.padding_top || 0; this.active_style.padding_top = a.padding_top || 0;
this.active_style.padding_bottom = a.style.padding_bottom || 0; this.active_style.padding_bottom = a.padding_bottom || 0;
this.active_style.padding_left = a.style.padding_left || 0; this.active_style.padding_left = a.padding_left || 0;
this.active_style.padding_right = a.style.padding_right || 0; this.active_style.padding_right = a.padding_right || 0;
if (this.active_style.padding_top == this.active_style.padding_bottom) { if (this.active_style.padding_top == this.active_style.padding_bottom) {
this.active_style.padding_vert = this.active_style.padding_top; this.active_style.padding_vert = this.active_style.padding_top;
@ -476,10 +476,10 @@ var SpacedeckSections = {
this.active_style.padding = this.active_style.padding_top; this.active_style.padding = this.active_style.padding_top;
} }
this.active_style.margin_top = a.style.margin_top || 0; this.active_style.margin_top = a.margin_top || 0;
this.active_style.margin_bottom = a.style.margin_bottom || 0; this.active_style.margin_bottom = a.margin_bottom || 0;
this.active_style.margin_left = a.style.margin_left || 0; this.active_style.margin_left = a.margin_left || 0;
this.active_style.margin_right = a.style.margin_right || 0; this.active_style.margin_right = a.margin_right || 0;
if (this.active_style.margin_top == this.active_style.margin_bottom) { if (this.active_style.margin_top == this.active_style.margin_bottom) {
this.active_style.margin_vert = this.active_style.margin_top; this.active_style.margin_vert = this.active_style.margin_top;
@ -758,8 +758,8 @@ var SpacedeckSections = {
}, },
resize_minimap: function() { resize_minimap: function() {
if (!this.active_space || !this.active_space.advanced) return; if (!this.active_space) return;
this.minimap_scale = this.active_space.advanced.width/100.0; this.minimap_scale = this.active_space.width/100.0;
}, },
handle_minimap_mouseup: function(evt) { handle_minimap_mouseup: function(evt) {
@ -921,7 +921,7 @@ var SpacedeckSections = {
discover_zones: function() { discover_zones: function() {
this.zones = _.sortBy(_.filter(this.active_space_artifacts, function(a) { return (a.mime=="x-spacedeck/zone") }), this.zones = _.sortBy(_.filter(this.active_space_artifacts, function(a) { return (a.mime=="x-spacedeck/zone") }),
function(z){return z.style.order}); function(z){return z.order});
}, },
artifact_plaintext: function(a) { artifact_plaintext: function(a) {
@ -1015,10 +1015,10 @@ var SpacedeckSections = {
arts = _.filter(arts); // remove any nulls arts = _.filter(arts); // remove any nulls
return { return {
x1: parseInt(_.min(arts.map(function(a){return ((!a.board || !a.board.x)?0:a.board.x)}))), x1: parseInt(_.min(arts.map(function(a){return ((!a || !a.x)?0:a.x)}))),
y1: parseInt(_.min(arts.map(function(a){return ((!a.board || !a.board.y)?0:a.board.y)}))), y1: parseInt(_.min(arts.map(function(a){return ((!a || !a.y)?0:a.y)}))),
x2: parseInt(_.max(arts.map(function(a){return (!a.board?0:a.board.x+a.board.w)}))), x2: parseInt(_.max(arts.map(function(a){return (!a?0:a.x+a.w)}))),
y2: parseInt(_.max(arts.map(function(a){return (!a.board?0:a.board.y+a.board.h)}))) y2: parseInt(_.max(arts.map(function(a){return (!a?0:a.y+a.h)})))
}; };
}, },
@ -1076,7 +1076,7 @@ var SpacedeckSections = {
this.selection_metrics.count=arts.length; this.selection_metrics.count=arts.length;
this.selection_metrics.scribble_selection = false; this.selection_metrics.scribble_selection = false;
if (arts.length == 1 && arts[0].mime == "x-spacedeck/vector") { if (arts.length == 1 && arts[0].mime == "x-spacedeck/vector") {
if (arts[0].style.shape == "scribble") { if (arts[0].shape == "scribble") {
this.selection_metrics.scribble_selection = true; this.selection_metrics.scribble_selection = true;
} }
this.selection_metrics.vector_points = arts[0].control_points; this.selection_metrics.vector_points = arts[0].control_points;
@ -1112,8 +1112,8 @@ var SpacedeckSections = {
fixup_space_size: function() { fixup_space_size: function() {
if (!this.active_space) return; if (!this.active_space) return;
this.active_space.advanced.width =Math.max(this.active_space.advanced.width, window.innerWidth); this.active_space.width =Math.max(this.active_space.width, window.innerWidth);
this.active_space.advanced.height=Math.max(this.active_space.advanced.height, window.innerHeight); this.active_space.height=Math.max(this.active_space.height, window.innerHeight);
}, },
end_transaction: function() { end_transaction: function() {
@ -1125,13 +1125,13 @@ var SpacedeckSections = {
var er = this.enclosing_rect(this.active_space_artifacts); var er = this.enclosing_rect(this.active_space_artifacts);
if (!er) return; if (!er) return;
this.active_space.advanced.width =Math.max(er.x2+100, window.innerWidth); this.active_space.width =Math.max(er.x2+100, window.innerWidth);
this.active_space.advanced.height=Math.max(er.y2+100, window.innerHeight); this.active_space.height=Math.max(er.y2+100, window.innerHeight);
if (this._last_bounds_width != this.active_space.advanced.width || if (this._last_bounds_width != this.active_space.width ||
this._last_bounds_height != this.active_space.advanced.height) { this._last_bounds_height != this.active_space.height) {
this._last_bounds_width = this.active_space.advanced.width; this._last_bounds_width = this.active_space.width;
this._last_bounds_height = this.active_space.advanced.height; this._last_bounds_height = this.active_space.height;
save_space(this.active_space); save_space(this.active_space);
} }
@ -1214,7 +1214,7 @@ var SpacedeckSections = {
// this is a bit hacky, but might be the smartest place to do it // this is a bit hacky, but might be the smartest place to do it
if (a.view && a.view.vector_svg) { if (a.view && a.view.vector_svg) {
a.style.shape_svg = a.view.vector_svg; a.shape_svg = a.view.vector_svg;
} }
window.artifact_save_queue[a._id] = a; window.artifact_save_queue[a._id] = a;
@ -1329,7 +1329,7 @@ var SpacedeckSections = {
this.update_selected_artifacts(function(a) { this.update_selected_artifacts(function(a) {
var c = {}; var c = {};
if (c[prop] != val) { if (a[prop] != val) {
//console.log("set_artifact_prop: ",c,val); //console.log("set_artifact_prop: ",c,val);
c[prop]=val; c[prop]=val;
return c; return c;
@ -1343,11 +1343,11 @@ var SpacedeckSections = {
this.begin_transaction(); this.begin_transaction();
this.update_selected_artifacts(function(a) { this.update_selected_artifacts(function(a) {
var c = {style: a.style||{}}; var c = {};
if (c.style[prop] != val) { if (a[prop] != val) {
//console.log("set_artifact_style_prop: ",c,val); //console.log("set_artifact_style_prop: ",c,val);
c.style[prop]=val; c[prop]=val;
return c; return c;
} }
@ -1419,7 +1419,7 @@ var SpacedeckSections = {
if (this.selected_artifacts().length!=1 && this.opened_dialog!="background") return; if (this.selected_artifacts().length!=1 && this.opened_dialog!="background") return;
if (this.opened_dialog=="background") { if (this.opened_dialog=="background") {
this.active_style[this.color_picker_target] = this.active_space.advanced.background_color; this.active_style[this.color_picker_target] = this.active_space.background_color;
} else { } else {
if (!this.active_style[this.color_picker_target]) { if (!this.active_style[this.color_picker_target]) {
this.active_style[this.color_picker_target] = this.default_style[this.color_picker_target]; this.active_style[this.color_picker_target] = this.default_style[this.color_picker_target];
@ -1478,10 +1478,8 @@ var SpacedeckSections = {
this.update_selected_artifacts(function(a) { this.update_selected_artifacts(function(a) {
return { return {
board: _.extend(a.board, { x: a.x+dx,
x: a.board.x+dx, y: a.y+dy
y: a.board.y+dy
})
}; };
}); });
}, },
@ -1489,7 +1487,7 @@ var SpacedeckSections = {
/* -------------------------------------------------------------------- */ /* -------------------------------------------------------------------- */
highest_z: function() { highest_z: function() {
var z = _.max(this.active_space_artifacts.map(function(a){return a.board.z||0})); var z = _.max(this.active_space_artifacts.map(function(a){return a.z||0}));
if (z<0) z=0; if (z<0) z=0;
if (z>999) z=999; if (z>999) z=999;
return z; return z;
@ -1574,20 +1572,18 @@ var SpacedeckSections = {
payload_thumbnail_web_uri: url || null, payload_thumbnail_web_uri: url || null,
space_id: space._id, space_id: space._id,
style: {
order: this.active_space_artifacts.length+1, order: this.active_space_artifacts.length+1,
valign: "middle", valign: "middle",
align: "center" align: "center"
//fill_color: "#f8f8f8" //fill_color: "#f8f8f8"
}
}; };
if (mimes[item_type] == "text/html") { if (mimes[item_type] == "text/html") {
new_item.style.padding_left = 10; new_item.padding_left = 10;
new_item.style.padding_top = 10; new_item.padding_top = 10;
new_item.style.padding_right = 10; new_item.padding_right = 10;
new_item.style.padding_bottom = 10; new_item.padding_bottom = 10;
new_item.style.fill_color = "rgba(255,255,255,1)"; new_item.fill_color = "rgba(255,255,255,1)";
new_item.description = "<p>Text</p>"; new_item.description = "<p>Text</p>";
} }
@ -1600,13 +1596,11 @@ var SpacedeckSections = {
z = point.z; z = point.z;
} }
new_item.board = { new_item.x = parseInt(point.x);
x: parseInt(point.x), new_item.y = parseInt(point.y);
y: parseInt(point.y), new_item.z = z;
w: w, new_item.w = w;
h: h, new_item.h = h;
z: z
};
if (this.guest_nickname) { if (this.guest_nickname) {
new_item.editor_name = this.guest_nickname; new_item.editor_name = this.guest_nickname;
@ -1665,7 +1659,7 @@ var SpacedeckSections = {
for (var i=0; i<new_zones.length; i++) { for (var i=0; i<new_zones.length; i++) {
if (new_zones[i]) { if (new_zones[i]) {
if (!new_zones[i].style) new_zones[i].style = {}; if (!new_zones[i].style) new_zones[i].style = {};
new_zones[i].style.order = i; new_zones[i].order = i;
save_artifact(new_zones[i]); save_artifact(new_zones[i]);
} }
} }
@ -1679,7 +1673,7 @@ var SpacedeckSections = {
for (var i=0; i<new_zones.length; i++) { for (var i=0; i<new_zones.length; i++) {
if (new_zones[i]) { if (new_zones[i]) {
if (!new_zones[i].style) new_zones[i].style = {}; if (!new_zones[i].style) new_zones[i].style = {};
new_zones[i].style.order = i; new_zones[i].order = i;
save_artifact(new_zones[i]); save_artifact(new_zones[i]);
} }
} }
@ -1695,17 +1689,13 @@ var SpacedeckSections = {
space_id: this.active_space._id, space_id: this.active_space._id,
mime: "x-spacedeck/zone", mime: "x-spacedeck/zone",
description: "Zone "+(this.zones.length+1), description: "Zone "+(this.zones.length+1),
board: {
x: point.x, x: point.x,
y: point.y, y: point.y,
w: w, w: w,
h: h, h: h,
z: 0 z: 0,
},
style: {
valign: "middle", valign: "middle",
align: "center" align: "center"
}
}; };
if (this.guest_nickname) { if (this.guest_nickname) {
@ -1735,14 +1725,11 @@ var SpacedeckSections = {
space_id: this.active_space._id, space_id: this.active_space._id,
mime: "x-spacedeck/shape", mime: "x-spacedeck/shape",
description: "Text", description: "Text",
board: {
x: point.x, x: point.x,
y: point.y, y: point.y,
z: point.z, z: point.z,
w: w, w: w,
h: h h: h,
},
style: {
stroke_color: "#ffffff", stroke_color: "#ffffff",
text_color: "#ffffff", text_color: "#ffffff",
stroke: 0, stroke: 0,
@ -1750,7 +1737,6 @@ var SpacedeckSections = {
shape: shape_type, shape: shape_type,
valign: "middle", valign: "middle",
align: "center" align: "center"
}
}; };
if (this.guest_nickname) { if (this.guest_nickname) {
@ -1829,18 +1815,14 @@ var SpacedeckSections = {
state: "uploading", state: "uploading",
payload_thumbnail_medium_uri: null, payload_thumbnail_medium_uri: null,
payload_thumbnail_web_uri: null, payload_thumbnail_web_uri: null,
board: {
x: point.x, x: point.x,
y: point.y, y: point.y,
w: w, w: w,
h: h, h: h,
z: point.z z: point.z,
},
style: {
order: this.active_space_artifacts.length+1, order: this.active_space_artifacts.length+1,
fill_color: fill fill_color: fill
} }
}
this.update_board_artifact_viewmodel(a); this.update_board_artifact_viewmodel(a);
@ -1864,7 +1846,11 @@ var SpacedeckSections = {
a.payload_thumbnail_big_uri = updated_a.payload_thumbnail_big_uri; a.payload_thumbnail_big_uri = updated_a.payload_thumbnail_big_uri;
a.payload_alternatives = updated_a.payload_alternatives; a.payload_alternatives = updated_a.payload_alternatives;
a.mime = updated_a.mime; a.mime = updated_a.mime;
a.board = updated_a.board; a.x = updated_a.x;
a.y = updated_a.y;
a.w = updated_a.w;
a.h = updated_a.h;
a.z = updated_a.z;
a.state = updated_a.state; a.state = updated_a.state;
this.update_board_artifact_viewmodel(a); this.update_board_artifact_viewmodel(a);
@ -2002,26 +1988,26 @@ var SpacedeckSections = {
clear_formatting_walk: function(el,cmd,arg1,arg2) { clear_formatting_walk: function(el,cmd,arg1,arg2) {
if (el && el.style) { if (el && el.style) {
if (cmd == "preciseFontSize") { if (cmd == "preciseFontSize") {
el.style.fontSize = null; el.fontSize = null;
} else if (cmd == "letterSpacing") { } else if (cmd == "letterSpacing") {
el.style.letterSpacing = null; el.letterSpacing = null;
} else if (cmd == "lineHeight") { } else if (cmd == "lineHeight") {
el.style.lineHeight = null; el.lineHeight = null;
} else if (cmd == "fontName") { } else if (cmd == "fontName") {
el.style.fontFamily = null; el.fontFamily = null;
} else if (cmd == "fontWeight") { } else if (cmd == "fontWeight") {
el.style.fontWeight = null; el.fontWeight = null;
el.style.fontStyle = null; el.fontStyle = null;
} else if (cmd == "bold") { } else if (cmd == "bold") {
el.style.fontWeight = null; el.fontWeight = null;
} else if (cmd == "italic") { } else if (cmd == "italic") {
el.style.fontStyle = null; el.fontStyle = null;
} else if (cmd == "underline") { } else if (cmd == "underline") {
el.style.textDecoration = null; el.textDecoration = null;
} else if (cmd == "strikeThrough") { } else if (cmd == "strikeThrough") {
el.style.textDecoration = null; el.textDecoration = null;
} else if (cmd == "forecolor") { } else if (cmd == "forecolor") {
el.style.color = null; el.color = null;
} }
} }
@ -2108,6 +2094,9 @@ var SpacedeckSections = {
if (a.description!=dom.innerHTML) { if (a.description!=dom.innerHTML) {
a.description = dom.innerHTML; a.description = dom.innerHTML;
console.log("new DOM:",dom.innerHTML);
this.update_board_artifact_viewmodel(a); this.update_board_artifact_viewmodel(a);
this.queue_artifact_for_save(a); this.queue_artifact_for_save(a);
@ -2141,10 +2130,7 @@ var SpacedeckSections = {
remove_link_from_selected_artifacts: function() { remove_link_from_selected_artifacts: function() {
this.update_selected_artifacts(function(a) { this.update_selected_artifacts(function(a) {
var meta = a.meta || {}; return {link_uri: ""};
delete meta.link_uri;
return {meta: meta};
}); });
}, },
@ -2160,9 +2146,7 @@ var SpacedeckSections = {
var insert_link_url = prompt("URL:",def); var insert_link_url = prompt("URL:",def);
this.update_selected_artifacts(function(a) { this.update_selected_artifacts(function(a) {
var meta = a.meta || {}; var update = {link_uri: insert_link_url};
meta.link_uri = insert_link_url;
var update = {meta: meta};
if (a.payload_uri && a.payload_uri.match("webgrabber")) { if (a.payload_uri && a.payload_uri.match("webgrabber")) {
var enc_uri = encodeURIComponent(btoa(insert_link_url)); var enc_uri = encodeURIComponent(btoa(insert_link_url));
@ -2185,11 +2169,10 @@ var SpacedeckSections = {
delete copy["$index"]; delete copy["$index"];
delete copy["_id"]; delete copy["_id"];
if (dx) copy.board.x += dx; if (dx) copy.x += dx;
if (dy) copy.board.y += dy; if (dy) copy.y += dy;
if (!copy.style) copy.style = {}; copy.order = this.active_space_artifacts.length+1;
copy.style.order = this.active_space_artifacts.length+1;
if (this.guest_nickname) { if (this.guest_nickname) {
copy.editor_name = this.guest_nickname; copy.editor_name = this.guest_nickname;
@ -2334,16 +2317,16 @@ var SpacedeckSections = {
if (parsed[i].mime) { if (parsed[i].mime) {
var z = this.highest_z()+1; var z = this.highest_z()+1;
if (parsed.length==1) { if (parsed.length==1) {
var w = parsed[i].board.w; var w = parsed[i].w;
var h = parsed[i].board.h; var h = parsed[i].h;
var point = this.find_place_for_item(w,h); var point = this.find_place_for_item(w,h);
parsed[i].board.x = point.x; parsed[i].x = point.x;
parsed[i].board.y = point.y; parsed[i].y = point.y;
parsed[i].board.z = point.z; parsed[i].z = point.z;
} else { } else {
parsed[i].board.x = parsed[i].board.x+50; parsed[i].x = parsed[i].x+50;
parsed[i].board.y = parsed[i].board.y+50; parsed[i].y = parsed[i].y+50;
parsed[i].board.y = parsed[i].board.z+z; parsed[i].y = parsed[i].z+z;
} }
this.clone_artifact(parsed[i], 0,0, function(a) { this.clone_artifact(parsed[i], 0,0, function(a) {
this.multi_select([a]); this.multi_select([a]);
@ -2373,13 +2356,11 @@ var SpacedeckSections = {
var h = 300; var h = 300;
var point = this.find_place_for_item(w,h); var point = this.find_place_for_item(w,h);
new_item.board = { new_item.x = point.x;
x: point.x, new_item.y = point.y;
y: point.y, new_item.w = w;
w: w, new_item.h = h;
h: h, new_item.z = point.z;
z: point.z
};
if (this.guest_nickname) { if (this.guest_nickname) {
new_item.editor_name = this.guest_nickname; new_item.editor_name = this.guest_nickname;
@ -2402,17 +2383,13 @@ var SpacedeckSections = {
mime: "image/png", mime: "image/png",
description: url, description: url,
state: "uploading", state: "uploading",
board: {
x: point.x, x: point.x,
y: point.y, y: point.y,
w: 200, w: 200,
h: 200, h: 200,
z: z z: z,
},
style: {
order: this.active_space_artifacts.length order: this.active_space_artifacts.length
} }
}
var metadata = parse_link(url) var metadata = parse_link(url)
@ -2473,16 +2450,12 @@ var SpacedeckSections = {
payload_thumbnail_medium_uri: metadata.thumbnail_url, payload_thumbnail_medium_uri: metadata.thumbnail_url,
payload_thumbnail_web_uri: metadata.thumbnail_url, payload_thumbnail_web_uri: metadata.thumbnail_url,
state: "idle", state: "idle",
meta: {
title: metadata.title, title: metadata.title,
link_uri: metadata.url || url link_uri: metadata.url || url,
},
board: {
x: point.x - w/2, x: point.x - w/2,
y: point.y - h/2, y: point.y - h/2,
w: w, w: w,
h: h h: h
}
}); });
if (this.guest_nickname) { if (this.guest_nickname) {
@ -2591,7 +2564,7 @@ var SpacedeckSections = {
}, },
remove_section_background: function() { remove_section_background: function() {
this.active_space.advanced.background_uri = null; this.active_space.background_uri = null;
save_space(this.active_space); save_space(this.active_space);
}, },
@ -2652,8 +2625,8 @@ var SpacedeckSections = {
this.bounds_zoom = this.viewport_zoom; this.bounds_zoom = this.viewport_zoom;
var eff_w = this.active_space.advanced.width*this.viewport_zoom; var eff_w = this.active_space.width*this.viewport_zoom;
var eff_h = this.active_space.advanced.height*this.viewport_zoom; var eff_h = this.active_space.height*this.viewport_zoom;
if (window.innerWidth>eff_w) { if (window.innerWidth>eff_w) {
// horizontal centering // horizontal centering
@ -2846,8 +2819,8 @@ var SpacedeckSections = {
var el = $("#space")[0]; var el = $("#space")[0];
var eff_w = this.active_space.advanced.width*this.viewport_zoom; var eff_w = this.active_space.width*this.viewport_zoom;
var eff_h = this.active_space.advanced.height*this.viewport_zoom; var eff_h = this.active_space.height*this.viewport_zoom;
var sx = el.scrollLeft; var sx = el.scrollLeft;
var sy = el.scrollTop; var sy = el.scrollTop;
@ -2980,9 +2953,9 @@ var SpacedeckSections = {
var w = 300; var w = 300;
var h = 200; var h = 200;
if (parsed.board && parsed.board.w && parsed.board.h) { if (parsed.board && parsed.w && parsed.h) {
w = parsed.board.w; w = parsed.w;
h = parsed.board.h; h = parsed.h;
} }
var point = this.cursor_point_to_space(evt); var point = this.cursor_point_to_space(evt);

View File

@ -283,9 +283,9 @@ var SpacedeckSpaces = {
this.discover_zones(); this.discover_zones();
window.setTimeout(function() { //window.setTimeout(function() {
this.zoom_to_fit(); // this.zoom_to_fit();
}.bind(this),10); //}.bind(this),10);
if (on_success) { if (on_success) {
on_success(); on_success();
@ -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

@ -15,7 +15,8 @@ SpacedeckUsers = {
account_remove_error: null, account_remove_error: null,
loading_user: false, loading_user: false,
password_reset_confirm_error: "", password_reset_confirm_error: "",
password_reset_error: "" password_reset_error: "",
}, },
methods:{ methods:{
load_user: function(on_success, on_error) { load_user: function(on_success, on_error) {
@ -29,6 +30,12 @@ SpacedeckUsers = {
if (on_success) { if (on_success) {
on_success(user); on_success(user);
} }
// see spacedeck_account.js
load_importables(this.user, function(files) {
this.importables = files;
}.bind(this));
}.bind(this), function() { }.bind(this), function() {
// error // error
this.loading_user = false; this.loading_user = false;
@ -40,18 +47,6 @@ SpacedeckUsers = {
}.bind(this)); }.bind(this));
}, },
login_google: function(evt) {
this.loading_user = true;
create_oauthtoken(function(data){
this.loading_user = false;
location.href = data.url;
}, function(xhr){
this.loading_user = false;
alert("could not get oauth token");
});
},
finalize_login: function(session_token, on_success) { finalize_login: function(session_token, on_success) {
if(!window.socket_auth || window.socket_auth == '' || window.socket_auth == 'null') { if(!window.socket_auth || window.socket_auth == '' || window.socket_auth == 'null') {
window.socket_auth = session_token; window.socket_auth = session_token;
@ -169,7 +164,6 @@ SpacedeckUsers = {
}, },
password_reset_submit: function(evt, email) { password_reset_submit: function(evt, email) {
if (evt) { if (evt) {
evt.preventDefault(); evt.preventDefault();
evt.stopPropagation(); evt.stopPropagation();
@ -203,7 +197,6 @@ SpacedeckUsers = {
}, },
password_reset_confirm: function(evt, password, password_confirmation) { password_reset_confirm: function(evt, password, password_confirmation) {
if (evt) { if (evt) {
evt.preventDefault(); evt.preventDefault();
evt.stopPropagation(); evt.stopPropagation();

View File

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

View File

@ -331,7 +331,7 @@ function setup_whiteboard_directives() {
var $scope = this.vm.$root; var $scope = this.vm.$root;
return _.filter($scope.active_space_artifacts, function(a) { return _.filter($scope.active_space_artifacts, function(a) {
return this.rects_intersecting(a.board, rect); return this.rects_intersecting(a, rect);
}.bind(this)); }.bind(this));
}, },
@ -439,15 +439,15 @@ function setup_whiteboard_directives() {
dists = $scope.unselected_artifacts().map(function(a){ dists = $scope.unselected_artifacts().map(function(a){
var r = this.rect_to_points(a.board); var r = this.rect_to_points(a);
var xd1 = Math.abs(r[0].x-x); var xd1 = Math.abs(r[0].x-x);
var xd2 = Math.abs(r[1].x-x); var xd2 = Math.abs(r[1].x-x);
var xd3 = Math.abs(r[0].x+a.board.w/2 - x); var xd3 = Math.abs(r[0].x+a.w/2 - x);
var yd1 = Math.abs(r[0].y-y); var yd1 = Math.abs(r[0].y-y);
var yd2 = Math.abs(r[2].y-y); var yd2 = Math.abs(r[2].y-y);
var yd3 = Math.abs(r[0].y+a.board.h/2 - y); var yd3 = Math.abs(r[0].y+a.h/2 - y);
if (!snap_middle) { if (!snap_middle) {
if (xd2<xd1) { if (xd2<xd1) {
@ -469,10 +469,10 @@ function setup_whiteboard_directives() {
if (snap_middle) { if (snap_middle) {
var xd = xd3; var xd = xd3;
var sx = r[0].x+a.board.w/2; var sx = r[0].x+a.w/2;
var yd = yd3; var yd = yd3;
var sy = r[0].y+a.board.h/2; var sy = r[0].y+a.h/2;
} }
return [[xd,sx],[yd,sy]]; return [[xd,sx],[yd,sy]];
@ -531,18 +531,14 @@ function setup_whiteboard_directives() {
mime: "x-spacedeck/vector", mime: "x-spacedeck/vector",
description: "", description: "",
control_points: [{dx:0,dy:0}], control_points: [{dx:0,dy:0}],
board: {
x: point.x, x: point.x,
y: point.y, y: point.y,
z: z, z: z,
w: 64, w: 64,
h: 64 h: 64,
},
style: {
stroke_color: "#000000", stroke_color: "#000000",
stroke: 2, stroke: 2,
shape: "scribble" shape: "scribble"
}
}; };
$scope.save_artifact(a, function(saved_a) { $scope.save_artifact(a, function(saved_a) {
@ -572,18 +568,14 @@ function setup_whiteboard_directives() {
mime: "x-spacedeck/vector", mime: "x-spacedeck/vector",
description: "", description: "",
control_points: [{dx:0,dy:0},{dx:0,dy:0},{dx:0,dy:0}], control_points: [{dx:0,dy:0},{dx:0,dy:0},{dx:0,dy:0}],
board: {
x: point.x, x: point.x,
y: point.y, y: point.y,
z: z, z: z,
w: 64, w: 64,
h: 64 h: 64,
},
style: {
stroke_color: "#000000", stroke_color: "#000000",
stroke: 2, stroke: 2,
shape: "arrow" shape: "arrow"
}
}; };
$scope.save_artifact(a, function(saved_a) { $scope.save_artifact(a, function(saved_a) {
@ -612,18 +604,14 @@ function setup_whiteboard_directives() {
mime: "x-spacedeck/vector", mime: "x-spacedeck/vector",
description: "", description: "",
control_points: [{dx:0,dy:0},{dx:0,dy:0}], control_points: [{dx:0,dy:0},{dx:0,dy:0}],
board: {
x: point.x, x: point.x,
y: point.y, y: point.y,
z: z, z: z,
w: 64, w: 64,
h: 64 h: 64,
},
style: {
stroke_color: "#000000", stroke_color: "#000000",
stroke: 2, stroke: 2,
shape: "line" shape: "line"
}
}; };
$scope.save_artifact(a, function(saved_a) { $scope.save_artifact(a, function(saved_a) {
@ -675,11 +663,11 @@ function setup_whiteboard_directives() {
if (_.include(["text","placeholder"],$scope.artifact_major_type(ars[i]))) { if (_.include(["text","placeholder"],$scope.artifact_major_type(ars[i]))) {
// some types of artifact need a minimum size // some types of artifact need a minimum size
if (ars[i].board.w<10) { if (ars[i].w<10) {
ars[i].board.w = 10; ars[i].w = 10;
} }
if (ars[i].board.h<10) { if (ars[i].h<10) {
ars[i].board.h = 10; ars[i].h = 10;
} }
} }
@ -827,10 +815,8 @@ function setup_whiteboard_directives() {
if (old_a) { if (old_a) {
return { return {
board: _.extend(a.board, { x: old_a.x + dx - snap_dx,
x: old_a.board.x + dx - snap_dx, y: old_a.y + dy - snap_dy
y: old_a.board.y + dy - snap_dy
})
}; };
} else { } else {
// deleted? // deleted?
@ -865,26 +851,24 @@ function setup_whiteboard_directives() {
var scale_x = lead_x ? (moved_x)/lead_x : 1; var scale_x = lead_x ? (moved_x)/lead_x : 1;
var scale_y = lead_y ? (moved_y)/lead_y : 1; var scale_y = lead_y ? (moved_y)/lead_y : 1;
if ($scope.transform_lock) scale_y = scale_x; if (!$scope.transform_lock) scale_y = scale_x;
$scope.update_selected_artifacts(function(a) { $scope.update_selected_artifacts(function(a) {
var old_a = $scope.find_artifact_before_transaction(a); var old_a = $scope.find_artifact_before_transaction(a);
var x1 = origin_x + ((old_a.board.x - origin_x) * scale_x); var x1 = origin_x + ((old_a.x - origin_x) * scale_x);
var y1 = origin_y + ((old_a.board.y - origin_y) * scale_y); var y1 = origin_y + ((old_a.y - origin_y) * scale_y);
var x2 = origin_x + (((old_a.board.x + old_a.board.w) - origin_x) * scale_x); var x2 = origin_x + (((old_a.x + old_a.w) - origin_x) * scale_x);
var y2 = origin_y + (((old_a.board.y + old_a.board.h) - origin_y) * scale_y); var y2 = origin_y + (((old_a.y + old_a.h) - origin_y) * scale_y);
if (x1>x2) { var t = x1; x1 = x2; x2 = t; } if (x1>x2) { var t = x1; x1 = x2; x2 = t; }
if (y1>y2) { var t = y1; y1 = y2; y2 = t; } if (y1>y2) { var t = y1; y1 = y2; y2 = t; }
return { return {
board: _.extend(a.board, {
x: x1, x: x1,
y: y1, y: y1,
w: x2 - x1, w: x2 - x1,
h: y2 - y1 h: y2 - y1
})
}; };
}.bind(this)); }.bind(this));
@ -902,18 +886,17 @@ function setup_whiteboard_directives() {
var old_a = $scope.find_artifact_before_transaction(a); var old_a = $scope.find_artifact_before_transaction(a);
var control_points = _.cloneDeep(old_a.control_points); var control_points = _.cloneDeep(old_a.control_points);
var board = _.clone(old_a.board);
var cp = control_points[$scope.selected_control_point_idx]; var cp = control_points[$scope.selected_control_point_idx];
var snapped = _this.snap_point(board.x+cp.dx+dx, board.y+cp.dy+dy); var snapped = _this.snap_point(old_a.x+cp.dx+dx, old_a.y+cp.dy+dy);
dx = snapped.snapx[1]-(board.x+cp.dx); dx = snapped.snapx[1]-(old_a.x+cp.dx);
dy = snapped.snapy[1]-(board.y+cp.dy); dy = snapped.snapy[1]-(old_a.y+cp.dy);
cp.dx += dx; cp.dx += dx;
cp.dy += dy; cp.dy += dy;
// special case for arrow's 3rd point // special case for arrow's 3rd point
if (a.style.shape == "arrow" && $scope.selected_control_point_idx!=2) { if (a.shape == "arrow" && $scope.selected_control_point_idx!=2) {
/*control_points[2].dx += dx/2; /*control_points[2].dx += dx/2;
control_points[2].dy += dy/2; */ control_points[2].dy += dy/2; */
@ -921,7 +904,7 @@ function setup_whiteboard_directives() {
control_points[2].dy = (control_points[0].dy+control_points[1].dy)/2; control_points[2].dy = (control_points[0].dy+control_points[1].dy)/2;
} }
return _this.normalize_control_points(control_points, board); return _this.normalize_control_points(control_points, old_a);
}); });
} else if (this.mouse_state == "scribble") { } else if (this.mouse_state == "scribble") {
@ -930,16 +913,14 @@ function setup_whiteboard_directives() {
var old_a = a; var old_a = a;
var control_points = _.cloneDeep(old_a.control_points); var control_points = _.cloneDeep(old_a.control_points);
var board = _.clone(old_a.board);
var offset = this.offset_point_in_wrapper({x:cursor.x,y:cursor.y}); var offset = this.offset_point_in_wrapper({x:cursor.x,y:cursor.y});
control_points.push({ control_points.push({
dx: offset.x-board.x, dx: offset.x-old_a.x,
dy: offset.y-board.y dy: offset.y-old_a.y
}); });
return this.normalize_control_points(simplify_scribble_points(control_points), board); return this.normalize_control_points(simplify_scribble_points(control_points), old_a);
}.bind(this)); }.bind(this));
var arts = $scope.selected_artifacts(); var arts = $scope.selected_artifacts();
@ -959,7 +940,7 @@ function setup_whiteboard_directives() {
} }
}, },
normalize_control_points: function(control_points, board) { normalize_control_points: function(control_points, artifact) {
var x1 = _.min(control_points,"dx").dx; var x1 = _.min(control_points,"dx").dx;
var y1 = _.min(control_points,"dy").dy; var y1 = _.min(control_points,"dy").dy;
var x2 = _.max(control_points,"dx").dx; var x2 = _.max(control_points,"dx").dx;
@ -981,19 +962,15 @@ function setup_whiteboard_directives() {
var bshiftx = 0; var bshiftx = 0;
var bshifty = 0; var bshifty = 0;
if (board.w < 0) bshiftx = -board.w; if (artifact.w < 0) bshiftx = -artifact.w;
if (board.h < 0) bshifty = -board.h; if (artifact.h < 0) bshifty = -artifact.h;
var shifted_board = {
x: board.x + bshiftx - shiftx,
y: board.y + bshifty - shifty,
w: w,
h: h,
z: board.z
};
return { return {
board: shifted_board, x: artifact.x + bshiftx - shiftx,
y: artifact.y + bshifty - shifty,
w: w,
h: h,
z: artifact.z,
control_points: shifted_cps control_points: shifted_cps
}; };
} }

View File

@ -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) {
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) {
} }
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) {
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 "";

View File

@ -1,57 +1,41 @@
"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 }
}).populate('space').exec((err,mem) => {
if (err) res.sendStatus(400);
else {
if (mem) { if (mem) {
if (!mem.user) { if (!mem.user) {
mem.code = null;
mem.state = "active"; mem.state = "active";
mem.user = req.user; mem.user_id = req.user._id;
mem.save(function(err){ mem.save().then(function() {
if (err) res.status(400).json(err);
else {
console.log(mem);
res.status(200).json(mem); res.status(200).json(mem);
}
}); });
} else { } else {
res.status(400).json({"error": "already_used"}); res.status(200).json(mem);
} }
} else { } else {
res.status(404).json({"error": "not found"}); res.status(404).json({"error": "not found"});
} }
}
}); });
} else { } else {
res.sendStatus(403); res.sendStatus(403);

View File

@ -1,10 +1,10 @@
"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,39 +12,42 @@ 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) {
res.status(400).json({});
return;
}
var email = req.body.email.toLowerCase(); var email = req.body.email.toLowerCase();
var password = req.body["password"]; var password = req.body["password"];
User.find({email: email, account_type: "email"}, (function (err, users) { db.User.findOne({where: {email: email}})
if (err) { .error(err => {
res.status(400).json({"error":"session.users"}); res.sendStatus(404);
} else { //res.status(400).json({"error":"session.users"});
})
if (users.length == 1) { .then(user => {
var user = users[0]; console.log("!!! user: ",user.password_hash);
if (bcrypt.compareSync(password, user.password_hash)) { if (bcrypt.compareSync(password, user.password_hash)) {
crypo.randomBytes(48, function(ex, buf) { crypto.randomBytes(48, function(ex, buf) {
var token = buf.toString('hex'); var token = buf.toString('hex');
console.log("!!! token: ",token);
var session = { var session = {
user_id: user._id,
token: token, token: token,
ip: req.ip, ip: req.ip,
device: "web", device: "web",
created_at: new Date() created_at: new Date()
}; };
if (!user.sessions) db.Session.create(session)
user.sessions = []; .error(err => {
console.error("Error creating Session:",err);
user.sessions.push(session); res.sendStatus(500);
})
user.save(function(err, result) { .then(() => {
if (err) console.error("Error saving user:",err);
var domain = (process.env.NODE_ENV == "production") ? new URL(config.get('endpoint')).hostname : "localhost"; var domain = (process.env.NODE_ENV == "production") ? new URL(config.get('endpoint')).hostname : "localhost";
res.cookie('sdsession', token, { domain: domain, httpOnly: true }); res.cookie('sdsession', token, { domain: domain, httpOnly: true });
res.status(201).json(session); res.status(201).json(session);
}); });
@ -52,28 +55,21 @@ router.post('/', function(req, res) {
} else { } else {
res.sendStatus(403); 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);
} }

View File

@ -1,7 +1,12 @@
"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');
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 = {
// 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) => {
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) {
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) {
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": {
updated_at: new Date()
}
});
res.distributeCreate("Artifact", artifact); 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) {
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) {
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);
@ -162,41 +164,22 @@ router.put('/:artifact_id', function(req, res, next) {
newAttr.last_update_editor_name = req.editor_name; newAttr.last_update_editor_name = req.editor_name;
} }
Artifact.findOneAndUpdate({ db.packArtifact(newAttr);
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 {
Space.update({
_id: req.space._id
}, {
"$set": {
updated_at: new Date()
}
});
res.distributeDelete("Artifact", artifact); res.distributeDelete("Artifact", artifact);
}
}); });
}); });

View File

@ -1,17 +1,15 @@
"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 = {
}; };
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,

View File

@ -1,8 +1,7 @@
"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');
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,18 +46,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 +57,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 +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) {
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) {
}); });
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;

View File

@ -1,57 +1,31 @@
"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,26 +33,26 @@ 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);
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) {
@ -104,7 +78,6 @@ router.post('/', function(req, res, next) {
}); });
res.status(201).json(membership); res.status(201).json(membership);
}
}); });
}); });
@ -125,22 +98,16 @@ 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);
else {
if (mem) { if (mem) {
var attrs = req.body; var attrs = req.body;
mem.role = attrs.role; mem.role = attrs.role;
mem.save(function(err) { mem.save(function() {
if (err) res.sendStatus(400);
else {
res.status(201).json(mem); res.status(201).json(mem);
}
}); });
} }
}
}); });
} else { } else {
res.sendStatus(403); res.sendStatus(403);
@ -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 {
mem.remove(function(err) {
if (err) {
res.status(400).json(err);
} else {
// FIXME might need to delete the user?
res.sendStatus(204); res.sendStatus(204);
}
}); });
}
}); });
} else { } else {
res.sendStatus(403); res.sendStatus(403);

View File

@ -1,6 +1,9 @@
"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');
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 = {
// 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']})
.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;
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 { db.Message.create(msg).then(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) { }}).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);
else {
if (msg) {
res.distributeDelete("Message", msg); res.distributeDelete("Message", msg);
} else {
res.sendStatus(404);
}
}
}); });
} }
}); });

View File

@ -1,6 +1,9 @@
"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');
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) {
}); });
} 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) {
}); });
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) {
}] }]
}; };
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,62 +102,55 @@ 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 db.Space
.find(q) .findAll(q)
.populate('creator', userMapping) .then(function(spaces) {
.exec(function(err, 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";
@ -167,12 +158,11 @@ 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
}) }, 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) {
}); });
} 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) {
} }
}); });
// 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) {
if (err) { db.Membership.create(membership).then(() => {
res.status(400).json(err);
} else {
res.status(201).json(createdSpace); 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) {
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) {
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
}, {
"new": true
}, function(err, space) {
if (err) res.status(400).json(err);
else {
res.distributeUpdate("Space", space); 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,29 +335,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.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": { 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);
@ -365,7 +355,6 @@ router.post('/:id/background', function(req, res, next) {
res.status(200).json(space); res.status(200).json(space);
} }
}); });
}
}); });
} }
}); });
@ -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) {
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);
else {
res.distributeDelete("Space", space); 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) {
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) {
}, 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) {
}); });
} 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({});

View File

@ -1,265 +0,0 @@
"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;

View File

@ -1,14 +1,15 @@
"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;
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,49 +32,64 @@ 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"]) {
res.status(400).json({"error":"email or password missing"});
return;
}
var email = req.body["email"].toLowerCase(); var email = req.body["email"].toLowerCase();
var nickname = req.body["nickname"]; var nickname = req.body["nickname"];
var password = req.body["password"]; var password = req.body["password"];
var password_confirmation = req.body["password_confirmation"]; var password_confirmation = req.body["password_confirmation"];
if (password_confirmation == password) { if (password_confirmation != password) {
if (validator.isEmail(email)) { res.status(400).json({"error":"password_confirmation"});
return;
}
if (!validator.isEmail(email)) {
res.status(400).json({"error":"email_invalid"});
return;
}
var createUser = function() { var createUser = function() {
bcrypt.genSalt(10, function(err, salt) { bcrypt.genSalt(10, function(err, salt) {
bcrypt.hash(password, salt, function(err, hash) { bcrypt.hash(password, salt, function(err, hash) {
crypto.randomBytes(16, function(ex, buf) {
crypo.randomBytes(16, function(ex, buf) {
var token = buf.toString('hex'); var token = buf.toString('hex');
var u = new User({ var u = {
_id: uuidv4(),
email: email, email: email,
account_type: "email", account_type: "email",
nickname: nickname, nickname: nickname,
password_hash: hash, password_hash: hash,
preferences: { prefs_language: req.i18n.locale,
language: req.i18n.locale
},
confirmation_token: token confirmation_token: token
}); };
u.save(function (err) { db.User.create(u)
if (err) res.sendStatus(400); .error(err => {
else { res.sendStatus(400);
var homeSpace = new Space({ })
.then(u => {
var homeSpace = {
_id: uuidv4(),
name: req.i18n.__("home"), name: req.i18n.__("home"),
space_type: "folder", space_type: "folder",
creator: u creator_id: u._id
}); };
db.Space.create(homeSpace)
homeSpace.save((err, homeSpace) => { .error(err => {
if (err) res.sendStatus(400); res.sendStatus(400);
else { })
.then(homeSpace => {
u.home_folder_id = homeSpace._id; u.home_folder_id = homeSpace._id;
u.save((err) => { u.save()
.then(() => {
res.status(201).json({});
mailer.sendMail(u.email, req.i18n.__("confirm_subject"), req.i18n.__("confirm_body"), { mailer.sendMail(u.email, req.i18n.__("confirm_subject"), req.i18n.__("confirm_body"), {
action: { action: {
@ -80,181 +97,30 @@ router.post('/', function(req, res) {
name: req.i18n.__("confirm_action") name: req.i18n.__("confirm_action")
} }
}); });
})
if (err) res.status(400).json(err); .error(err => {
else { res.status(400).json(err);
res.status(201).json({});
}
}); });
} })
});
}
}); });
}); });
}); });
}); });
}; };
User.find({email: email}, (function (err, users) { db.User.findAll({where: {email: email}})
if (err) { .then(users => {
res.status(400).json({"error":"password_confirmation"}); if (users.length == 0) {
} else { //var domain = email.slice(email.lastIndexOf('@')+1);
if (users.length === 0) {
var domain = email.slice(email.lastIndexOf('@')+1);
Domain.findOne({domain: domain}, function(err, domain) {
if(domain){
if(domain.edu) {
createUser(); createUser();
} else {
res.status(400).json({"error":"domain_blocked"});
}
} else {
createUser();
}
});
} else { } else {
res.status(400).json({"error":"user_email_already_used"}); 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"});
}
}); });
router.get('/oauth2callback/url', function(req, res) { router.get('/current', function(req, res, next) {
var google = require('googleapis');
var OAuth2 = google.auth.OAuth2;
var oauth2Client = new OAuth2(
config.google_access,
config.google_secret,
config.endpoint + "/login"
);
var url = oauth2Client.generateAuthUrl({
access_type: 'online',
scope: "email"
});
res.status(200).json({"url":url});
});
router.get('/loginorsignupviagoogle', function(req, res) {
var google = require('googleapis');
var OAuth2 = google.auth.OAuth2;
var plus = google.plus('v1');
var oauth2Client = new OAuth2(
config.google_access,
config.google_secret,
config.endpoint + "/login"
);
var loginUser = function(user, cb) {
crypo.randomBytes(48, function(ex, buf) {
var token = buf.toString('hex');
var session = {
token: token,
created_at: new Date()
};
if(!user.sessions)
user.sessions = [];
user.sessions.push(session);
user.save(function(err, user) {
cb(session);
});
});
};
var code = req.query.code;
oauth2Client.getToken(code, function(err, tokens) {
if (err) res.status(400).json(err);
else {
var apiUrl = "https://www.googleapis.com/oauth2/v1/userinfo?alt=json&access_token=" + tokens.access_token;
var finalizeLogin = function(session){
res.cookie('sdsession', session.token, { httpOnly: true });
res.status(201).json(session);
};
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);
});
}
});
}
});
}
});
}
});
}
});
}
});
});
router.get('/ ', 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) {
}); });
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) {
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.status(400).json(err);
}else{
res.sendStatus(204); res.sendStatus(204);
}
}); });
}); });
}); });
@ -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) {
res.sendStatus(400);
} else {
fs.unlink(localResizedFilePath, (err) => { fs.unlink(localResizedFilePath, (err) => {
if (err) { if (err) {
console.error(err); console.error(err);
res.status(400).json(err); res.status(400).json(err);
} else { } else {
res.status(200).json(updatedUser); res.status(200).json(user);
} }
}); });
}
}); });
} }
}); });
@ -400,32 +254,21 @@ 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) {
res.status(400).json(err);
} else {
if (user) { if (user) {
if(user.account_type == "email") { crypto.randomBytes(16, (ex, buf) => {
crypo.randomBytes(16, (ex, buf) => {
user.password_reset_token = buf.toString('hex'); user.password_reset_token = buf.toString('hex');
user.save((err, updatedUser) => { user.save().then(updatedUser => {
if (err) res.status(400).json(err);
else {
mailer.sendMail(email, req.i18n.__("password_reset_subject"), req.i18n.__("password_reset_body"), {action: { mailer.sendMail(email, req.i18n.__("password_reset_subject"), req.i18n.__("password_reset_body"), {action: {
link: config.endpoint + "/password-confirm/" + user.password_reset_token, link: config.endpoint + "/password-confirm/" + user.password_reset_token,
name: req.i18n.__("password_reset_action") name: req.i18n.__("password_reset_action")
}}); }});
res.status(201).json({}); res.status(201).json({});
}
}); });
}); });
} else { } else {
res.status(404).json({"error": "error_unknown_email"}); res.status(404).json({"error": "error_unknown_email"});
} }
} else {
res.status(404).json({"error": "error_unknown_email"});
}
}
}); });
}); });
@ -433,11 +276,8 @@ 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) {
res.sendStatus(400);
} else {
if (user) { if (user) {
bcrypt.genSalt(10, (err, salt) => { bcrypt.genSalt(10, (err, salt) => {
bcrypt.hash(password, salt, function(err, hash) { bcrypt.hash(password, salt, function(err, hash) {
@ -456,7 +296,6 @@ router.post('/password_reset_requests/:confirm_token/confirm', function(req, res
} else { } else {
res.sendStatus(404); res.sendStatus(404);
} }
}
}); });
}); });
@ -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");

View File

@ -1,7 +1,7 @@
"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');

View File

@ -1,7 +1,7 @@
"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');
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) => {
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) => {
} 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) {

169
spacedeck.js Normal file
View File

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

View File

@ -1,6 +1,6 @@
<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 @@
<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 @@
<div class="modal-section labels-inline"> <div class="modal-section labels-inline">
<div class="form-group"> <div class="form-group">
<label class="checkbox" <label class="checkbox"
v-bind:class="{checked: user.preferences.email_notifications}" v-bind:class="{checked: user.prefs_email_notifications}"
v-on:click="account_save_user_notifications(!user.preferences.email_notifications);"> v-on:click="account_save_user_notifications(!user.prefs_email_notifications);">
<span>[[__('notifications_option_chat')]]</span> <span>[[__('notifications_option_chat')]]</span>
</label> </label>
</div> </div>

View File

@ -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

@ -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 @@
<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 @@
<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 @@
<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 @@
<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 @@
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>

View File

@ -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 @@
<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 -->

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.

View File

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

View File

@ -9,7 +9,7 @@
<button v-on:click="add_zone()" class="btn btn-sm btn-primary">[[__("add_zone")]]</button> <button v-on:click="add_zone()" class="btn btn-sm btn-primary">[[__("add_zone")]]</button>
</div> </div>
<div class="dialog-section no-p" v-for="z in zones | orderBy 'style.order'" style="white-space: nowrap;text-align:left;cursor:pointer" v-on:click="zoom_to_zone(z)"> <div class="dialog-section no-p" v-for="z in zones | orderBy 'order'" style="white-space: nowrap;text-align:left;cursor:pointer" v-on:click="zoom_to_zone(z)">
<button class="btn btn-sm btn-transparent">{{{z.description}}}</button> <button class="btn btn-sm btn-transparent">{{{z.description}}}</button>
<button v-if="$index==current_zone_idx" v-on:click="sort_zone_up(z)" class="btn btn-sm btn-round btn-transparent btn-icon"><span class="icon icon-triangle-up"></span></button> <button v-if="$index==current_zone_idx" v-on:click="sort_zone_up(z)" class="btn btn-sm btn-round btn-transparent btn-icon"><span class="icon icon-triangle-up"></span></button>
<button v-if="$index==current_zone_idx" v-on:click="sort_zone_down(z)" class="btn btn-sm btn-round btn-transparent btn-icon"><span class="icon icon-triangle-down"></span></button> <button v-if="$index==current_zone_idx" v-on:click="sort_zone_down(z)" class="btn btn-sm btn-round btn-transparent btn-icon"><span class="icon icon-triangle-down"></span></button>

View File

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