Port Backend to SQLite/Sequelize (removes MongoDB), Support Electron (#14)
* The MongoDB/Mongoose data storage is removed in favor of Sequelize. This abstracts over SQLite or RDBMs like PostgreSQL and MSSQL. The default is SQLite, which significantly simplifies deployments in end-user environments. * As Spacedeck now has no more mandatory server dependencies, we can wrap it in Electron and ship it as a desktop application. * Removes docker-compose.yml * First version of import UI
This commit is contained in:
parent
8e0bc69a11
commit
ebac854da8
38
README.md
38
README.md
@ -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).
|
||||
|
||||
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.
|
||||
|
||||
@ -23,20 +23,21 @@ We appreciate filed issues, pull requests and general discussion.
|
||||
|
||||
Spacedeck uses the following major building blocks:
|
||||
|
||||
- Vue.js: Frontend UI Framework
|
||||
- Node.js 7.x: Web Server / API
|
||||
- MongoDB 3.4: Data store *(important: newer versions than 3.4 don't work yet!)*
|
||||
- Redis 3.x: Data store for realtime channels, (*optional*)
|
||||
- Node.js 9.x: Web Server / API
|
||||
- Vue.js: Frontend UI Framework (included)
|
||||
- SQLite (included)
|
||||
|
||||
It also has some binary dependencies for media conversion and PDF export:
|
||||
It also has some optional binary dependencies for advanced media conversion:
|
||||
|
||||
- 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.
|
||||
|
||||
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
|
||||
|
||||
@ -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)
|
||||
|
||||
# Run
|
||||
# Run (web server)
|
||||
|
||||
export NODE_ENV=development
|
||||
npm start
|
||||
open http://localhost:9666
|
||||
node spacedeck.js
|
||||
|
||||
Then open http://localhost:9666 in a web browser.
|
||||
|
||||
# Run (desktop app with integrated web server)
|
||||
|
||||
electron .
|
||||
|
||||
# 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
|
||||
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
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
|
197
app.js
197
app.js
@ -1,180 +1,33 @@
|
||||
"use strict";
|
||||
const spacedeck = require('./spacedeck')
|
||||
|
||||
require('./models/schema');
|
||||
require("log-timestamp");
|
||||
const electron = require('electron')
|
||||
const electronApp = electron.app
|
||||
const BrowserWindow = electron.BrowserWindow
|
||||
let mainWindow
|
||||
|
||||
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 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')));
|
||||
function createWindow () {
|
||||
mainWindow = new BrowserWindow({width: 1200, height: 700})
|
||||
mainWindow.loadURL("http://localhost:9666")
|
||||
mainWindow.on('closed', function () {
|
||||
mainWindow = null
|
||||
})
|
||||
}
|
||||
|
||||
app.use(bodyParser.json({
|
||||
limit: '50mb'
|
||||
}));
|
||||
electronApp.on('ready', createWindow)
|
||||
|
||||
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())
|
||||
|
||||
// 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
|
||||
}));
|
||||
// Quit when all windows are closed.
|
||||
electronApp.on('window-all-closed', function () {
|
||||
// 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') {
|
||||
electronApp.quit()
|
||||
}
|
||||
})
|
||||
|
||||
// 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'));
|
||||
electronApp.on('activate', function () {
|
||||
// On OS X it's common to re-create a window in the app when the
|
||||
// dock icon is clicked and there are no other windows open.
|
||||
if (mainWindow === null) {
|
||||
createWindow()
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
});
|
||||
})
|
||||
|
@ -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
|
@ -4,52 +4,18 @@ const exec = require('child_process');
|
||||
const gm = require('gm');
|
||||
const async = require('async');
|
||||
const fs = require('fs');
|
||||
const Models = require('../models/schema');
|
||||
const Models = require('../models/db');
|
||||
const uploader = require('../helpers/uploader');
|
||||
const path = require('path');
|
||||
const os = require('os');
|
||||
|
||||
const fileExtensionMap = {
|
||||
".amr" : "audio/AMR",
|
||||
".ogg" : "audio/ogg",
|
||||
".aac" : "audio/aac",
|
||||
".mp3" : "audio/mpeg",
|
||||
".mpg" : "video/mpeg",
|
||||
".3ga" : "audio/3ga",
|
||||
".mp4" : "video/mp4",
|
||||
".wav" : "audio/wav",
|
||||
".mov" : "video/quicktime",
|
||||
".doc" : "application/msword",
|
||||
".dot" : "application/msword",
|
||||
".docx" : "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||||
".dotx" : "application/vnd.openxmlformats-officedocument.wordprocessingml.template",
|
||||
".docm" : "application/vnd.ms-word.document.macroEnabled.12",
|
||||
".dotm" : "application/vnd.ms-word.template.macroEnabled.12",
|
||||
".xls" : "application/vnd.ms-excel",
|
||||
".xlt" : "application/vnd.ms-excel",
|
||||
".xla" : "application/vnd.ms-excel",
|
||||
".xlsx" : "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
".xltx" : "application/vnd.openxmlformats-officedocument.spreadsheetml.template",
|
||||
".xlsm" : "application/vnd.ms-excel.sheet.macroEnabled.12",
|
||||
".xltm" : "application/vnd.ms-excel.template.macroEnabled.12",
|
||||
".xlam" : "application/vnd.ms-excel.addin.macroEnabled.12",
|
||||
".xlsb" : "application/vnd.ms-excel.sheet.binary.macroEnabled.12",
|
||||
".ppt" : "application/vnd.ms-powerpoint",
|
||||
".pot" : "application/vnd.ms-powerpoint",
|
||||
".pps" : "application/vnd.ms-powerpoint",
|
||||
".ppa" : "application/vnd.ms-powerpoint",
|
||||
".pptx" : "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
||||
".potx" : "application/vnd.openxmlformats-officedocument.presentationml.template",
|
||||
".ppsx" : "application/vnd.openxmlformats-officedocument.presentationml.slideshow",
|
||||
".ppam" : "application/vnd.ms-powerpoint.addin.macroEnabled.12",
|
||||
".pptm" : "application/vnd.ms-powerpoint.presentation.macroEnabled.12",
|
||||
".potm" : "application/vnd.ms-powerpoint.template.macroEnabled.12",
|
||||
".ppsm" : "application/vnd.ms-powerpoint.slideshow.macroEnabled.12",
|
||||
".key" : "application/x-iwork-keynote-sffkey",
|
||||
".pages" : "application/x-iwork-pages-sffpages",
|
||||
".numbers" : "application/x-iwork-numbers-sffnumbers",
|
||||
".ttf" : "application/x-font-ttf"
|
||||
};
|
||||
const db = require('../models/db');
|
||||
const Sequelize = require('sequelize');
|
||||
const Op = Sequelize.Op;
|
||||
|
||||
const mime = require('mime-types');
|
||||
const fileType = require('file-type');
|
||||
const readChunk = require('read-chunk');
|
||||
|
||||
const convertableImageTypes = [
|
||||
"image/png",
|
||||
@ -69,7 +35,7 @@ const convertableVideoTypes = [
|
||||
|
||||
const convertableAudioTypes = [
|
||||
"application/ogg",
|
||||
"audio/AMR",
|
||||
"audio/amr",
|
||||
"audio/3ga",
|
||||
"audio/wav",
|
||||
"audio/3gpp",
|
||||
@ -128,7 +94,7 @@ function createWaveform(fileName, localFilePath, callback){
|
||||
|
||||
function convertVideo(fileName, filePath, codec, callback, progress_callback) {
|
||||
var ext = path.extname(fileName);
|
||||
var presetMime = fileExtensionMap[ext];
|
||||
var presetMime = mime.lookup(fileName);
|
||||
|
||||
var newExt = codec == "mp4" ? "mp4" : "ogv";
|
||||
var convertedPath = filePath + "." + newExt;
|
||||
@ -186,7 +152,7 @@ function convertVideo(fileName, filePath, codec, callback, progress_callback) {
|
||||
|
||||
function convertAudio(fileName, filePath, codec, callback) {
|
||||
var ext = path.extname(fileName);
|
||||
var presetMime = fileExtensionMap[ext];
|
||||
var presetMime = mime.lookup(fileName);
|
||||
|
||||
var newExt = codec == "mp3" ? "mp3" : "ogg";
|
||||
var convertedPath = filePath + "." + newExt;
|
||||
@ -223,22 +189,14 @@ function createThumbnailForVideo(fileName, filePath, callback) {
|
||||
|
||||
function getMime(fileName, filePath, callback) {
|
||||
var ext = path.extname(fileName);
|
||||
var presetMime = fileExtensionMap[ext];
|
||||
var presetMime = mime.lookup(fileName);
|
||||
|
||||
if (presetMime) {
|
||||
callback(null, presetMime);
|
||||
} else {
|
||||
exec.execFile("file", ["-b","--mime-type", filePath], {}, function(error, stdout, stderr) {
|
||||
console.log("file stdout: ",stdout);
|
||||
if (stderr === '' && error == null) {
|
||||
//filter special chars from commandline
|
||||
var mime = stdout.replace(/[^a-zA-Z0-9\/\-]/g,'');
|
||||
callback(null, mime);
|
||||
} else {
|
||||
console.log("getMime file error: ", error);
|
||||
callback(error, null);
|
||||
}
|
||||
});
|
||||
const buffer = readChunk.sync(filePath, 0, 4100);
|
||||
var mimeType = fileType(buffer);
|
||||
callback(null, mimeType);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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({
|
||||
small: function(callback){
|
||||
resizeAndUpload(a, size, 320, fileName, imageFilePath, callback);
|
||||
@ -285,13 +243,13 @@ var resizeAndUploadImage = function(a, mime, size, fileName, fileNameOrg, imageF
|
||||
},
|
||||
original: function(callback){
|
||||
var s3Key = "s"+ a.space_id.toString() + "/a" + a._id + "/" + fileNameOrg;
|
||||
uploader.uploadFile(s3Key, mime, originalFilePath, function(err, url){
|
||||
uploader.uploadFile(s3Key, mimeType, originalFilePath, function(err, url){
|
||||
callback(null, url);
|
||||
});
|
||||
}
|
||||
}, function(err, results) {
|
||||
a.state = "idle";
|
||||
a.mime = mime;
|
||||
a.mime = mimeType;
|
||||
var stats = fs.statSync(originalFilePath);
|
||||
|
||||
a.payload_size = stats["size"];
|
||||
@ -301,15 +259,11 @@ var resizeAndUploadImage = function(a, mime, size, fileName, fileNameOrg, imageF
|
||||
a.payload_uri = results.original;
|
||||
|
||||
var factor = 320/size.width;
|
||||
var newBoardSpecs = a.board;
|
||||
newBoardSpecs.w = Math.round(size.width*factor);
|
||||
newBoardSpecs.h = Math.round(size.height*factor);
|
||||
a.board = newBoardSpecs;
|
||||
a.w = Math.round(size.width*factor);
|
||||
a.h = Math.round(size.height*factor);
|
||||
|
||||
a.updated_at = new Date();
|
||||
a.save(function(err) {
|
||||
if(err) payloadCallback(err, null);
|
||||
else {
|
||||
a.save().then(function() {
|
||||
fs.unlink(originalFilePath, function (err) {
|
||||
if (err){
|
||||
console.error(err);
|
||||
@ -319,28 +273,27 @@ var resizeAndUploadImage = function(a, mime, size, fileName, fileNameOrg, imageF
|
||||
payloadCallback(null, a);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
convert: function(a, fileName, localFilePath, payloadCallback, progress_callback) {
|
||||
getMime(fileName, localFilePath, function(err, mime){
|
||||
console.log("[convert] fn: "+fileName+" local: "+localFilePath+" mime:", mime);
|
||||
getMime(fileName, localFilePath, function(err, mimeType){
|
||||
console.log("[convert] fn: "+fileName+" local: "+localFilePath+" mimeType:", mimeType);
|
||||
|
||||
if (!err) {
|
||||
if (convertableImageTypes.indexOf(mime) > -1) {
|
||||
if (convertableImageTypes.indexOf(mimeType) > -1) {
|
||||
|
||||
gm(localFilePath).size(function (err, size) {
|
||||
console.log("[convert] gm:", err, size);
|
||||
|
||||
if (!err) {
|
||||
if(mime == "application/pdf") {
|
||||
if(mimeType == "application/pdf") {
|
||||
var firstImagePath = localFilePath + ".jpeg";
|
||||
exec.execFile("gs", ["-sDEVICE=jpeg","-dNOPAUSE", "-dJPEGQ=80", "-dBATCH", "-dFirstPage=1", "-dLastPage=1", "-sOutputFile=" + firstImagePath, "-r90", "-f", localFilePath], {}, function(error, stdout, stderr) {
|
||||
if(error === null) {
|
||||
resizeAndUploadImage(a, mime, size, fileName + ".jpeg", fileName, firstImagePath, localFilePath, function(err, a) {
|
||||
resizeAndUploadImage(a, mimeType, size, fileName + ".jpeg", fileName, firstImagePath, localFilePath, function(err, a) {
|
||||
fs.unlink(firstImagePath, function (err) {
|
||||
payloadCallback(err, a);
|
||||
});
|
||||
@ -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
|
||||
|
||||
var s3Key = "s"+ a.space_id.toString() + "/a" + a._id.toString() + "/" + fileName;
|
||||
@ -362,7 +315,7 @@ module.exports = {
|
||||
var stats = fs.statSync(localFilePath);
|
||||
|
||||
a.state = "idle";
|
||||
a.mime = mime;
|
||||
a.mime = mimeType;
|
||||
|
||||
a.payload_size = stats["size"];
|
||||
a.payload_thumbnail_web_uri = url;
|
||||
@ -371,15 +324,11 @@ module.exports = {
|
||||
a.payload_uri = url;
|
||||
|
||||
var factor = 320/size.width;
|
||||
var newBoardSpecs = a.board;
|
||||
newBoardSpecs.w = Math.round(size.width*factor);
|
||||
newBoardSpecs.h = Math.round(size.height*factor);
|
||||
a.board = newBoardSpecs;
|
||||
a.w = Math.round(size.width*factor);
|
||||
a.h = Math.round(size.height*factor);
|
||||
|
||||
a.updated_at = new Date();
|
||||
a.save(function(err){
|
||||
if(err) payloadCallback(err, null);
|
||||
else {
|
||||
a.save().then(function() {
|
||||
fs.unlink(localFilePath, function (err) {
|
||||
if (err){
|
||||
console.error(err);
|
||||
@ -389,18 +338,17 @@ module.exports = {
|
||||
payloadCallback(null, a);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
} else {
|
||||
resizeAndUploadImage(a, mime, size, fileName, fileName, localFilePath, localFilePath, payloadCallback);
|
||||
resizeAndUploadImage(a, mimeType, size, fileName, fileName, localFilePath, localFilePath, payloadCallback);
|
||||
}
|
||||
} else payloadCallback(err);
|
||||
});
|
||||
|
||||
} else if (convertableVideoTypes.indexOf(mime) > -1) {
|
||||
} else if (convertableVideoTypes.indexOf(mimeType) > -1) {
|
||||
async.parallel({
|
||||
thumbnail: function(callback) {
|
||||
createThumbnailForVideo(fileName, localFilePath, function(err, created){
|
||||
@ -416,7 +364,7 @@ module.exports = {
|
||||
});
|
||||
},
|
||||
ogg: function(callback) {
|
||||
if (mime == "video/ogg") {
|
||||
if (mimeType == "video/ogg") {
|
||||
callback(null, "org");
|
||||
} else {
|
||||
convertVideo(fileName, localFilePath, "ogg", function(err, file) {
|
||||
@ -432,7 +380,7 @@ module.exports = {
|
||||
}
|
||||
},
|
||||
mp4: function(callback) {
|
||||
if (mime == "video/mp4") {
|
||||
if (mimeType == "video/mp4") {
|
||||
callback(null, "org");
|
||||
} else {
|
||||
convertVideo(fileName, localFilePath, "mp4", function(err, file) {
|
||||
@ -448,7 +396,7 @@ module.exports = {
|
||||
}
|
||||
},
|
||||
original: function(callback){
|
||||
uploader.uploadFile(fileName, mime, localFilePath, function(err, url){
|
||||
uploader.uploadFile(fileName, mimeType, localFilePath, function(err, url){
|
||||
callback(null, url);
|
||||
});
|
||||
}
|
||||
@ -458,7 +406,7 @@ module.exports = {
|
||||
if (err) payloadCallback(err, a);
|
||||
else {
|
||||
a.state = "idle";
|
||||
a.mime = mime;
|
||||
a.mime = mimeType;
|
||||
var stats = fs.statSync(localFilePath);
|
||||
|
||||
a.payload_size = stats["size"];
|
||||
@ -467,7 +415,7 @@ module.exports = {
|
||||
a.payload_thumbnail_big_uri = results.thumbnail;
|
||||
a.payload_uri = results.original;
|
||||
|
||||
if (mime == "video/mp4") {
|
||||
if (mimeType == "video/mp4") {
|
||||
a.payload_alternatives = [
|
||||
{
|
||||
mime: "video/ogg",
|
||||
@ -483,6 +431,8 @@ module.exports = {
|
||||
];
|
||||
}
|
||||
|
||||
db.packArtifact(a);
|
||||
|
||||
a.updated_at = new Date();
|
||||
a.save(function(err) {
|
||||
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({
|
||||
ogg: function(callback) {
|
||||
@ -539,7 +489,7 @@ module.exports = {
|
||||
},
|
||||
original: function(callback) {
|
||||
var keyName = "s" + a.space_id.toString() + "/a" + a._id.toString() + "/" + fileName;
|
||||
uploader.uploadFile(keyName, mime, localFilePath, function(err, url){
|
||||
uploader.uploadFile(keyName, mimeType, localFilePath, function(err, url){
|
||||
callback(null, url);
|
||||
});
|
||||
}
|
||||
@ -550,7 +500,7 @@ module.exports = {
|
||||
else {
|
||||
|
||||
a.state = "idle";
|
||||
a.mime = mime;
|
||||
a.mime = mimeType;
|
||||
var stats = fs.statSync(localFilePath);
|
||||
|
||||
a.payload_size = stats["size"];
|
||||
@ -564,9 +514,10 @@ module.exports = {
|
||||
];
|
||||
|
||||
a.updated_at = new Date();
|
||||
a.save(function(err){
|
||||
if(err) payloadCallback(err, null);
|
||||
else {
|
||||
|
||||
db.packArtifact(a);
|
||||
|
||||
a.save().then(function(){
|
||||
fs.unlink(localFilePath, function (err) {
|
||||
if (err){
|
||||
console.error(err);
|
||||
@ -576,31 +527,27 @@ module.exports = {
|
||||
payloadCallback(null, a);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
} else {
|
||||
console.log("mime not matched for conversion, storing file");
|
||||
console.log("mimeType not matched for conversion, storing file");
|
||||
var keyName = "s" + a.space_id.toString() + "/a" + a._id.toString() + "/" + fileName;
|
||||
uploader.uploadFile(keyName, mime, localFilePath, function(err, url) {
|
||||
uploader.uploadFile(keyName, mimeType, localFilePath, function(err, url) {
|
||||
|
||||
a.state = "idle";
|
||||
a.mime = mime;
|
||||
a.mime = mimeType;
|
||||
var stats = fs.statSync(localFilePath);
|
||||
a.payload_size = stats["size"];
|
||||
a.payload_uri = url;
|
||||
|
||||
a.updated_at = new Date();
|
||||
a.save(function(err) {
|
||||
if(err) payloadCallback(err, null);
|
||||
else {
|
||||
a.save().then(function() {
|
||||
fs.unlink(localFilePath, function (err) {
|
||||
payloadCallback(null, a);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -5,7 +5,12 @@ const config = require('config')
|
||||
const fs = require('fs')
|
||||
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 = {
|
||||
importZIP: function(user, zipPath) {
|
||||
@ -54,8 +59,8 @@ module.exports = {
|
||||
let artifacts = JSON.parse(fs.readFileSync(importDir+'/'+space._id+'_artifacts.json'))
|
||||
console.log('[import] space',space._id,'artifacts:',artifacts.length)
|
||||
|
||||
let q = {_id: space._id}
|
||||
space.creator = user._id
|
||||
//let q = {where: {_id: space._id}}
|
||||
space.creator_id = user._id
|
||||
delete space.__v
|
||||
|
||||
// transplant homefolder
|
||||
@ -64,18 +69,36 @@ module.exports = {
|
||||
space.parent_space_id = user.home_folder_id
|
||||
}
|
||||
|
||||
Space.findOneAndUpdate(q, space, {upsert: true}, function(err,res) {
|
||||
if (err) console.log("[import] space upsert err:",err)
|
||||
// move nested attrs
|
||||
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++) {
|
||||
let a = artifacts[j]
|
||||
|
||||
let q = {_id: a._id}
|
||||
a.creator = user._id
|
||||
a.user_id = user._id
|
||||
delete a.__v
|
||||
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/"
|
||||
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
|
||||
@ -92,8 +115,10 @@ module.exports = {
|
||||
}
|
||||
}
|
||||
|
||||
Artifact.findOneAndUpdate(q, a, {upsert: true}, function(err,res) {
|
||||
if (err) console.log("[import] artifact upsert err:",err)
|
||||
db.packArtifact(a)
|
||||
|
||||
db.Artifact.create(a).error(function(err) {
|
||||
console.error("[import] artifact upsert err:",err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
var swig = require('swig');
|
||||
var AWS = require('aws-sdk');
|
||||
//var AWS = require('aws-sdk');
|
||||
|
||||
module.exports = {
|
||||
sendMail: (to_email, subject, body, options) => {
|
||||
@ -29,9 +29,9 @@ module.exports = {
|
||||
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);
|
||||
} else {
|
||||
/*} else {
|
||||
AWS.config.update({region: 'eu-west-1'});
|
||||
var ses = new AWS.SES();
|
||||
|
||||
@ -56,6 +56,6 @@ module.exports = {
|
||||
if (err) console.error("Error sending email:", err);
|
||||
else console.log("Email sent.");
|
||||
});
|
||||
}
|
||||
}*/
|
||||
}
|
||||
};
|
||||
|
@ -1,8 +1,9 @@
|
||||
'use strict';
|
||||
|
||||
require('../models/schema');
|
||||
var config = require('config');
|
||||
var phantom = require('node-phantom-simple');
|
||||
const db = require('../models/db');
|
||||
const config = require('config');
|
||||
const phantom = require('node-phantom-simple');
|
||||
const os = require('os');
|
||||
|
||||
module.exports = {
|
||||
// type = "pdf" or "png"
|
||||
@ -10,7 +11,7 @@ module.exports = {
|
||||
var spaceId = space._id;
|
||||
var space_url = config.get("endpoint")+"/api/spaces/"+spaceId+"/html";
|
||||
|
||||
var export_path = "/tmp/"+spaceId+"."+type;
|
||||
var export_path = os.tmpdir()+"/"+spaceId+"."+type;
|
||||
|
||||
var timeout = 5000;
|
||||
if (type=="pdf") timeout = 30000;
|
||||
@ -24,7 +25,7 @@ module.exports = {
|
||||
|
||||
var on_exit = function(exit_code) {
|
||||
if (exit_code>0) {
|
||||
console.log("phantom abnormal exit for url "+space_url);
|
||||
console.error("phantom abnormal exit for url "+space_url);
|
||||
if (!on_success_called && on_error) {
|
||||
on_error();
|
||||
}
|
||||
@ -33,15 +34,15 @@ module.exports = {
|
||||
|
||||
phantom.create({ path: require('phantomjs-prebuilt').path }, function (err, browser) {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
console.error(err);
|
||||
} else {
|
||||
return browser.createPage(function (err, page) {
|
||||
console.log("page created, opening ",space_url);
|
||||
|
||||
if (type=="pdf") {
|
||||
var psz = {
|
||||
width: space.advanced.width+"px",
|
||||
height: space.advanced.height+"px"
|
||||
width: space.width+"px",
|
||||
height: space.height+"px"
|
||||
};
|
||||
page.set('paperSize', psz);
|
||||
}
|
||||
|
@ -1,14 +1,16 @@
|
||||
'use strict';
|
||||
require('../models/schema');
|
||||
|
||||
const db = require('../models/db');
|
||||
const Sequelize = require('sequelize');
|
||||
const Op = Sequelize.Op;
|
||||
|
||||
const config = require('config');
|
||||
|
||||
const WebSocketServer = require('ws').Server;
|
||||
|
||||
const RedisConnection = require('ioredis');
|
||||
//const RedisConnection = require('ioredis');
|
||||
const async = require('async');
|
||||
const _ = require("underscore");
|
||||
const mongoose = require("mongoose");
|
||||
const crypto = require('crypto');
|
||||
|
||||
const redisMock = require("./redis.js");
|
||||
@ -45,11 +47,11 @@ module.exports = {
|
||||
const editorAuth = msg.editor_auth;
|
||||
const spaceId = msg.space_id;
|
||||
|
||||
Space.findOne({"_id": spaceId}).populate('creator').exec((err, space) => {
|
||||
db.Space.findOne({where: {"_id": spaceId}}).then(space => {
|
||||
if (space) {
|
||||
const upgradeSocket = function() {
|
||||
if (token) {
|
||||
User.findBySessionToken(token, function(err, user) {
|
||||
db.findUserBySessionToken(token, function(err, user) {
|
||||
if (err) {
|
||||
console.error(err, user);
|
||||
} else {
|
||||
@ -271,7 +273,7 @@ module.exports = {
|
||||
if (!spaceId)
|
||||
return;
|
||||
|
||||
this.state.smembers("space_" + spaceId, function(err, list) {
|
||||
/*this.state.smembers("space_" + spaceId, function(err, list) {
|
||||
async.map(list, function(item, callback) {
|
||||
this.state.get(item, function(err, userId) {
|
||||
console.log(item, "->", userId);
|
||||
@ -292,16 +294,14 @@ module.exports = {
|
||||
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) {
|
||||
if (err)
|
||||
console.error(err);
|
||||
else {
|
||||
db.User.findAll({where: {
|
||||
"_id" : { "$in" : validUserIds }}, attributes: ["nickname","email","avatar_thumbnail_uri"]})
|
||||
.then(users) {
|
||||
const allUsers = users.concat(anonymousUsers);
|
||||
const strUsers = JSON.stringify({users: allUsers, space_id: spaceId});
|
||||
this.state.publish("users", strUsers);
|
||||
}
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
}.bind(this));
|
||||
}.bind(this));*/
|
||||
}
|
||||
};
|
||||
|
@ -1,6 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
require('../models/schema');
|
||||
require('../models/db');
|
||||
var config = require('config');
|
||||
|
||||
module.exports = (req, res, next) => {
|
||||
|
@ -1,9 +1,11 @@
|
||||
'use strict';
|
||||
|
||||
require('../models/schema');
|
||||
require('../models/db');
|
||||
var config = require('config');
|
||||
const redis = require('../helpers/redis');
|
||||
|
||||
// FIXME TODO object.toJSON()
|
||||
|
||||
var saveAction = (actionKey, object) => {
|
||||
if (object.constructor.modelName == "Space")
|
||||
return;
|
||||
@ -13,14 +15,14 @@ var saveAction = (actionKey, object) => {
|
||||
space: object.space_id || object.space,
|
||||
user: object.user_id || object.user,
|
||||
editor_name: object.editor_name,
|
||||
object: object.toJSON()
|
||||
object: object
|
||||
};
|
||||
|
||||
let action = new Action(attr);
|
||||
/*let action = new Action(attr);
|
||||
action.save(function(err) {
|
||||
if (err)
|
||||
console.error("saved create action err:", err);
|
||||
});
|
||||
});*/
|
||||
};
|
||||
|
||||
module.exports = (req, res, next) => {
|
||||
@ -32,21 +34,21 @@ module.exports = (req, res, next) => {
|
||||
|
||||
res['distributeCreate'] = function(model, object) {
|
||||
if (!object) return;
|
||||
redis.sendMessage("create", model, object.toJSON(), req.channelId);
|
||||
this.status(201).json(object.toJSON());
|
||||
redis.sendMessage("create", model, object, req.channelId);
|
||||
this.status(201).json(object);
|
||||
saveAction("create", object);
|
||||
};
|
||||
|
||||
res['distributeUpdate'] = function(model, object) {
|
||||
if (!object) return;
|
||||
redis.sendMessage("update", model, object.toJSON(), req.channelId);
|
||||
this.status(200).json(object.toJSON());
|
||||
redis.sendMessage("update", model, object, req.channelId);
|
||||
this.status(200).json(object);
|
||||
saveAction("update", object);
|
||||
};
|
||||
|
||||
res['distributeDelete'] = function(model, object) {
|
||||
if (!object) return;
|
||||
redis.sendMessage("delete", model, object.toJSON(), req.channelId);
|
||||
redis.sendMessage("delete", model, object, req.channelId);
|
||||
this.sendStatus(204);
|
||||
saveAction("delete", object);
|
||||
};
|
||||
|
@ -1,22 +1,20 @@
|
||||
'use strict';
|
||||
const db = require('../models/db');
|
||||
const Sequelize = require('sequelize');
|
||||
const Op = Sequelize.Op;
|
||||
|
||||
require('../models/schema');
|
||||
var config = require('config');
|
||||
|
||||
module.exports = (req, res, next) => {
|
||||
var artifactId = req.params.artifact_id;
|
||||
Artifact.findOne({
|
||||
db.Artifact.findOne({where: {
|
||||
"_id": artifactId
|
||||
}, (err, artifact) => {
|
||||
if (err) {
|
||||
res.status(400).json(err);
|
||||
} else {
|
||||
}}).then(artifact => {
|
||||
if (artifact) {
|
||||
req['artifact'] = artifact;
|
||||
next();
|
||||
} else {
|
||||
res.sendStatus(404);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
@ -1,6 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
require('../models/schema');
|
||||
require('../models/db');
|
||||
const config = require('config');
|
||||
const url = require('url');
|
||||
|
||||
@ -33,13 +33,13 @@ module.exports = (req, res, next) => {
|
||||
|
||||
respond(origin, req, res, next);
|
||||
} else {
|
||||
Team.getTeamForHost(parsedUrl.hostname, (err, team, subdomain) => {
|
||||
if (team) {
|
||||
//Team.getTeamForHost(parsedUrl.hostname, (err, team, subdomain) => {
|
||||
//if (team) {
|
||||
respond(origin, req, res, next);
|
||||
} else {
|
||||
//} else {
|
||||
next();
|
||||
}
|
||||
});
|
||||
//}
|
||||
//});
|
||||
}
|
||||
|
||||
} else {
|
||||
|
@ -1,6 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
require('../models/schema');
|
||||
require('../models/db');
|
||||
var config = require('config');
|
||||
|
||||
module.exports = (req, res, next) => {
|
||||
@ -10,8 +10,8 @@ module.exports = (req, res, next) => {
|
||||
req.i18n.setLocaleFromCookie();
|
||||
}
|
||||
|
||||
if (req.user && req.user.preferences.language) {
|
||||
req.i18n.setLocale(req.user.preferences.language);
|
||||
if (req.user && req.user.prefs_language) {
|
||||
req.i18n.setLocale(req.user.prefs_language);
|
||||
}
|
||||
next();
|
||||
}
|
46
middlewares/session.js
Normal file
46
middlewares/session.js
Normal 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();
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
require('../models/schema');
|
||||
const db = require('../models/db');
|
||||
var config = require('config');
|
||||
|
||||
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 role = "none";
|
||||
|
||||
@ -77,7 +33,7 @@ module.exports = (req, res, next) => {
|
||||
}
|
||||
|
||||
if (req.user) {
|
||||
rolePerUser(space, req.user, function(newRole) {
|
||||
db.getUserRoleInSpace(space, req.user, function(newRole) {
|
||||
if (newRole == "admin" && (role == "editor" || role == "viewer")) {
|
||||
finalizeReq(space, newRole);
|
||||
} else if (newRole == "editor" && (role == "viewer")) {
|
||||
@ -97,16 +53,17 @@ module.exports = (req, res, next) => {
|
||||
'email': 1
|
||||
};
|
||||
|
||||
Space.findOne({
|
||||
db.Space.findOne({where: {
|
||||
"_id": spaceId
|
||||
}).populate("creator", userMapping).exec(function(err, space) {
|
||||
if (err) {
|
||||
res.status(400).json(err);
|
||||
} else {
|
||||
}}).then(function(space) {
|
||||
|
||||
//.populate("creator", userMapping)
|
||||
//if (err) {
|
||||
// res.status(400).json(err);
|
||||
//} else {
|
||||
|
||||
if (space) {
|
||||
|
||||
if (space.access_mode == "public") {
|
||||
|
||||
if (space.password) {
|
||||
if (req.spacePassword) {
|
||||
if (req.spacePassword === space.password) {
|
||||
@ -126,6 +83,8 @@ module.exports = (req, res, next) => {
|
||||
}
|
||||
|
||||
} else {
|
||||
// space is private
|
||||
|
||||
// special permission for screenshot/pdf export from backend
|
||||
if (req.query['api_token'] && req.query['api_token'] == config.get('phantom_api_secret')) {
|
||||
finalizeReq(space, "viewer");
|
||||
@ -133,7 +92,7 @@ module.exports = (req, res, next) => {
|
||||
}
|
||||
|
||||
if (req.user) {
|
||||
rolePerUser(space, req.user, function(role) {
|
||||
db.getUserRoleInSpace(space, req.user, function(role) {
|
||||
if (role == "none") {
|
||||
finalizeAnonymousLogin(space, req["spaceAuth"]);
|
||||
} else {
|
||||
@ -155,6 +114,5 @@ module.exports = (req, res, next) => {
|
||||
"error": "space_not_found"
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
});
|
||||
}
|
@ -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"
|
||||
});
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
@ -1,5 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
// FIXME port this last model
|
||||
|
||||
var mongoose = require('mongoose');
|
||||
var Schema = mongoose.Schema;
|
||||
|
||||
|
@ -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
347
models/db.js
Normal 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;
|
||||
}
|
||||
}
|
@ -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
|
||||
});
|
@ -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
|
||||
});
|
||||
|
@ -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
|
||||
});
|
@ -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;
|
@ -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);
|
273
models/space.js
273
models/space.js
@ -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;
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
};
|
53
package.json
53
package.json
@ -3,8 +3,7 @@
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "nodemon -e .js,.html bin/www",
|
||||
"test": "mocha"
|
||||
"start": "electron ."
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=7.8.0"
|
||||
@ -12,76 +11,42 @@
|
||||
"dependencies": {
|
||||
"archiver": "1.3.0",
|
||||
"async": "2.3.0",
|
||||
"aws-sdk": "2.39.0",
|
||||
"basic-auth": "1.1.0",
|
||||
"bcryptjs": "2.4.3",
|
||||
"body-parser": "~1.17.1",
|
||||
"cheerio": "0.22.0",
|
||||
"config": "1.25.1",
|
||||
"cookie-parser": "~1.4.3",
|
||||
"csurf": "1.9.0",
|
||||
"debug": "~2.6.3",
|
||||
"electron": "^1.8.4",
|
||||
"execSync": "latest",
|
||||
"express": "~4.13.0",
|
||||
"extract-zip": "^1.6.6",
|
||||
"file-type": "^7.6.0",
|
||||
"glob": "7.1.1",
|
||||
"gm": "1.23.0",
|
||||
"googleapis": "18.0.0",
|
||||
"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",
|
||||
"i18n-2": "0.6.3",
|
||||
"ioredis": "2.5.0",
|
||||
"lodash": "^4.3.0",
|
||||
"log-timestamp": "latest",
|
||||
"md5": "2.2.1",
|
||||
"morgan": "1.8.1",
|
||||
"mock-aws-s3": "^2.6.0",
|
||||
"moment": "^2.19.3",
|
||||
"mongoose": "4.9.3",
|
||||
"morgan": "1.8.1",
|
||||
"node-phantom-simple": "2.2.4",
|
||||
"node-sass-middleware": "0.11.0",
|
||||
"pdfkit": "0.8.0",
|
||||
"phantomjs-prebuilt": "2.1.14",
|
||||
"pm2": "latest",
|
||||
"qr-image": "3.2.0",
|
||||
"raven": "1.2.0",
|
||||
"read-chunk": "^2.1.0",
|
||||
"request": "2.81.0",
|
||||
"sanitize-html": "^1.11.1",
|
||||
"sequelize": "^4.37.6",
|
||||
"serve-favicon": "~2.4.2",
|
||||
"serve-static": "^1.13.1",
|
||||
"slug": "0.9.1",
|
||||
"sqlite3": "^4.0.0",
|
||||
"swig": "1.4.2",
|
||||
"underscore": "1.8.3",
|
||||
"uuid": "^3.2.1",
|
||||
"validator": "7.0.0",
|
||||
"weak": "1.0.1",
|
||||
"ws": "2.2.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"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"
|
||||
},
|
||||
"main": "app.js",
|
||||
"description": "",
|
||||
"main": "Gulpfile.js",
|
||||
"directories": {},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -133,6 +133,14 @@ function load_spaces(id, is_home, on_success, 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) {
|
||||
load_resource("get", "/spaces?writablefolders=true", null, on_success, on_error);
|
||||
}
|
||||
|
@ -8,23 +8,28 @@ SpacedeckAccount = {
|
||||
account_confirmed_sent: false,
|
||||
account_tab: 'invoices',
|
||||
password_change_error: null,
|
||||
feedback_text: ""
|
||||
feedback_text: "",
|
||||
importables: [], // spacedeck.com zip import files
|
||||
},
|
||||
methods: {
|
||||
show_account: function(user) {
|
||||
show_account: function() {
|
||||
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) {
|
||||
this.user.preferences.daily_digest = val;
|
||||
this.user.prefs_email_digest = val;
|
||||
this.save_user(function() {
|
||||
});
|
||||
},
|
||||
|
||||
account_save_user_notifications: function(val) {
|
||||
this.user.preferences.email_notifications = val;
|
||||
this.user.prefs_email_notifications = val;
|
||||
this.save_user(function() {
|
||||
});
|
||||
},
|
||||
@ -36,13 +41,11 @@ SpacedeckAccount = {
|
||||
|
||||
save_user_language: function(lang) {
|
||||
localStorage.lang = lang;
|
||||
if (this.user.preferences) {
|
||||
this.user.preferences.language = lang;
|
||||
this.user.prefs_language = lang;
|
||||
this.save_user(function() {
|
||||
window._spacedeck_location_change = true;
|
||||
location.href="/spaces";
|
||||
}.bind(this));
|
||||
}
|
||||
},
|
||||
|
||||
save_user: function(on_success) {
|
||||
|
@ -61,16 +61,16 @@ var SpacedeckBoardArtifacts = {
|
||||
},
|
||||
|
||||
artifact_link: function(a) {
|
||||
if (a.meta && a.meta.link_uri) {
|
||||
return a.meta.link_uri;
|
||||
if (a.link_uri) {
|
||||
return a.link_uri;
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
},
|
||||
|
||||
artifact_link_caption: function(a) {
|
||||
if (a.meta && a.meta.link_uri) {
|
||||
var parts = a.meta.link_uri.split("/");
|
||||
if (a.link_uri) {
|
||||
var parts = a.link_uri.split("/");
|
||||
// scheme://domain.foo/...
|
||||
// 0 1 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 (!a._id) clzs.push("creating");
|
||||
|
||||
if (a.style) {
|
||||
clzs.push("align-"+a.style.align);
|
||||
clzs.push("align-"+a.style.valign);
|
||||
}
|
||||
if (a.align) clzs.push("align-"+a.align);
|
||||
if (a.valign) clzs.push("align-"+a.valign);
|
||||
|
||||
clzs.push("state-"+a.state);
|
||||
|
||||
@ -123,56 +121,56 @@ var SpacedeckBoardArtifacts = {
|
||||
artifact_inner_style: function(a) {
|
||||
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 (a.style.stroke) {
|
||||
styles.push("border-width:"+a.style.stroke+"px");
|
||||
styles.push("border-style:"+(a.style.stroke_style||"solid"));
|
||||
if (a.stroke) {
|
||||
styles.push("border-width:"+a.stroke+"px");
|
||||
styles.push("border-style:"+(a.stroke_style||"solid"));
|
||||
}
|
||||
if (a.style.stroke_color) {
|
||||
styles.push("border-color:"+a.style.stroke_color);
|
||||
if (a.stroke_color) {
|
||||
styles.push("border-color:"+a.stroke_color);
|
||||
}
|
||||
if (a.style.border_radius) {
|
||||
styles.push("border-radius:"+a.style.border_radius+"px");
|
||||
if (a.border_radius) {
|
||||
styles.push("border-radius:"+a.border_radius+"px");
|
||||
}
|
||||
}
|
||||
|
||||
if (a.style.fill_color && !svg_style) {
|
||||
styles.push("background-color:"+a.style.fill_color);
|
||||
if (a.fill_color && !svg_style) {
|
||||
styles.push("background-color:"+a.fill_color);
|
||||
}
|
||||
if (a.style.text_color) {
|
||||
styles.push("color:"+a.style.text_color);
|
||||
if (a.text_color) {
|
||||
styles.push("color:"+a.text_color);
|
||||
}
|
||||
|
||||
var filters = [];
|
||||
|
||||
if (!isNaN(a.style.brightness) && a.style.brightness != 100) {
|
||||
filters.push("brightness("+a.style.brightness+"%)");
|
||||
if (!isNaN(a.brightness) && a.brightness != 100) {
|
||||
filters.push("brightness("+a.brightness+"%)");
|
||||
}
|
||||
if (!isNaN(a.style.contrast) && a.style.contrast != 100) {
|
||||
filters.push("contrast("+a.style.contrast+"%)");
|
||||
if (!isNaN(a.contrast) && a.contrast != 100) {
|
||||
filters.push("contrast("+a.contrast+"%)");
|
||||
}
|
||||
if (!isNaN(a.style.opacity) && a.style.opacity != 100) {
|
||||
filters.push("opacity("+a.style.opacity+"%)");
|
||||
if (!isNaN(a.opacity) && a.opacity != 100) {
|
||||
filters.push("opacity("+a.opacity+"%)");
|
||||
}
|
||||
if (!isNaN(a.style.hue) && a.style.hue) {
|
||||
filters.push("hue-rotate("+a.style.hue+"deg)");
|
||||
if (!isNaN(a.hue) && a.hue) {
|
||||
filters.push("hue-rotate("+a.hue+"deg)");
|
||||
}
|
||||
if (!isNaN(a.style.saturation) && a.style.saturation != 100) {
|
||||
filters.push("saturate("+a.style.saturation+"%)");
|
||||
if (!isNaN(a.saturation) && a.saturation != 100) {
|
||||
filters.push("saturate("+a.saturation+"%)");
|
||||
}
|
||||
if (!isNaN(a.style.blur) && a.style.blur) {
|
||||
filters.push("blur("+a.style.blur+"px)");
|
||||
if (!isNaN(a.blur) && a.blur) {
|
||||
filters.push("blur("+a.blur+"px)");
|
||||
}
|
||||
|
||||
if (filters.length) {
|
||||
styles.push("-webkit-filter:"+filters.join(" "));
|
||||
styles.push("filter:"+filters.join(" "));
|
||||
}
|
||||
}
|
||||
//}
|
||||
|
||||
return styles.join(";");
|
||||
},
|
||||
@ -180,12 +178,10 @@ var SpacedeckBoardArtifacts = {
|
||||
artifact_text_cell_style: function(a, for_text_editor) {
|
||||
var styles = [];
|
||||
|
||||
if (a.style) {
|
||||
if (a.style.padding_left) styles.push("padding-left:"+a.style.padding_left+"px");
|
||||
if (a.style.padding_right) styles.push("padding-right:"+a.style.padding_right+"px");
|
||||
if (a.style.padding_top) styles.push("padding-top:"+a.style.padding_top+"px");
|
||||
if (a.style.padding_bottom) styles.push("padding-bottom:"+a.style.padding_bottom+"px");
|
||||
}
|
||||
if (a.padding_left) styles.push("padding-left:"+a.padding_left+"px");
|
||||
if (a.padding_right) styles.push("padding-right:"+a.padding_right+"px");
|
||||
if (a.padding_top) styles.push("padding-top:"+a.padding_top+"px");
|
||||
if (a.padding_bottom) styles.push("padding-bottom:"+a.padding_bottom+"px");
|
||||
|
||||
return styles.join(";");
|
||||
},
|
||||
@ -194,25 +190,21 @@ var SpacedeckBoardArtifacts = {
|
||||
var styles = [];
|
||||
var z = 0;
|
||||
|
||||
if (a.board) {
|
||||
z = a.board.z;
|
||||
z = a.z;
|
||||
if (z<0) z=0; // fix negative z-index
|
||||
|
||||
styles = [
|
||||
"left:" +a.board.x+"px",
|
||||
"top:" +a.board.y+"px",
|
||||
"width:" +a.board.w+"px",
|
||||
"height:"+a.board.h+"px",
|
||||
"left:" +a.x+"px",
|
||||
"top:" +a.y+"px",
|
||||
"width:" +a.w+"px",
|
||||
"height:"+a.h+"px",
|
||||
"z-index:"+z
|
||||
];
|
||||
}
|
||||
|
||||
if (a.style) {
|
||||
if (a.style.margin_left) styles.push("margin-left:"+a.style.margin_left+"px");
|
||||
if (a.style.margin_right) styles.push("margin-right:"+a.style.margin_right+"px");
|
||||
if (a.style.margin_top) styles.push("margin-top:"+a.style.margin_top+"px");
|
||||
if (a.style.margin_bottom) styles.push("margin-bottom:"+a.style.margin_bottom+"px");
|
||||
}
|
||||
if (a.margin_left) styles.push("margin-left:"+a.margin_left+"px");
|
||||
if (a.margin_right) styles.push("margin-right:"+a.margin_right+"px");
|
||||
if (a.margin_top) styles.push("margin-top:"+a.margin_top+"px");
|
||||
if (a.margin_bottom) styles.push("margin-bottom:"+a.margin_bottom+"px");
|
||||
|
||||
// FIXME: via class logic?
|
||||
if (a.mime.match("vector")) {
|
||||
@ -241,7 +233,7 @@ var SpacedeckBoardArtifacts = {
|
||||
|
||||
artifact_thumbnail_uri: function(a) {
|
||||
if (a.payload_thumbnail_big_uri && a.board) {
|
||||
if (a.board.w>800) {
|
||||
if (a.w>800) {
|
||||
return a.payload_thumbnail_big_uri;
|
||||
}
|
||||
}
|
||||
@ -255,35 +247,35 @@ var SpacedeckBoardArtifacts = {
|
||||
var type = parts[0];
|
||||
var provider = parts[1];
|
||||
|
||||
if (!a.meta || !a.meta.link_uri) {
|
||||
if (!a.link_uri) {
|
||||
console.log("missing meta / link_uri: ",a);
|
||||
console.log("type/provider: ",type,provider);
|
||||
return ("missing metadata: "+a._id);
|
||||
}
|
||||
|
||||
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) {
|
||||
var uri = "https://youtube.com/embed/"+vid[2];
|
||||
return "<iframe frameborder=0 allowfullscreen src=\""+uri+"?showinfo=0&rel=0&controls=0\"></iframe>";
|
||||
} else return "Can't resolve: "+a.payload_uri;
|
||||
|
||||
} 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) {
|
||||
var uri = "https://www.dailymotion.com/embed/video/"+match[1];
|
||||
return "<iframe frameborder=0 allowfullscreen src=\""+uri+"\"></iframe>";
|
||||
} else return "Can't resolve: "+a.payload_uri;
|
||||
|
||||
} 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) {
|
||||
var uri = "https://player.vimeo.com/video/"+match[2];
|
||||
return "<iframe frameborder=0 allowfullscreen src=\""+uri+"\"></iframe>";
|
||||
} else return "Can't resolve: "+a.payload_uri;
|
||||
|
||||
} 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") {
|
||||
|
||||
@ -299,8 +291,8 @@ var SpacedeckBoardArtifacts = {
|
||||
|
||||
if (mtype != "vector" && mtype != "shape") return "";
|
||||
|
||||
var shape = a.style.shape || "";
|
||||
var padding = 32 + a.style.stroke*2;
|
||||
var shape = a.shape || "";
|
||||
var padding = 32 + a.stroke*2;
|
||||
var path_svg;
|
||||
var fill = "";
|
||||
|
||||
@ -310,13 +302,13 @@ var SpacedeckBoardArtifacts = {
|
||||
fill = "fill:none";
|
||||
} else {
|
||||
path_svg = render_vector_shape(a, padding);
|
||||
fill = "fill:"+a.style.fill_color+";";
|
||||
fill = "fill:"+a.fill_color+";";
|
||||
padding = 0;
|
||||
}
|
||||
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)+"' ";
|
||||
svg += "style='margin-left:"+(-margin)+"px;margin-top:"+(-margin)+"px;stroke-width:"+a.style.stroke+";stroke:"+a.style.stroke_color+";"+fill+"'>";
|
||||
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.stroke+";stroke:"+a.stroke_color+";"+fill+"'>";
|
||||
svg += path_svg;
|
||||
svg += "</svg>";
|
||||
|
||||
@ -329,10 +321,10 @@ var SpacedeckBoardArtifacts = {
|
||||
if (arts.length==0) return null;
|
||||
|
||||
r = {
|
||||
x1: parseInt(_.min(arts.map(function(a){return a.board.x}))),
|
||||
y1: parseInt(_.min(arts.map(function(a){return a.board.y}))),
|
||||
x2: parseInt(_.max(arts.map(function(a){return a.board.x+a.board.w}))),
|
||||
y2: parseInt(_.max(arts.map(function(a){return a.board.y+a.board.h})))
|
||||
x1: parseInt(_.min(arts.map(function(a){return a.x}))),
|
||||
y1: parseInt(_.min(arts.map(function(a){return a.y}))),
|
||||
x2: parseInt(_.max(arts.map(function(a){return a.x+a.w}))),
|
||||
y2: parseInt(_.max(arts.map(function(a){return a.y+a.h})))
|
||||
};
|
||||
r.x=r.x1;
|
||||
r.y=r.y1;
|
||||
@ -356,7 +348,7 @@ var SpacedeckBoardArtifacts = {
|
||||
|
||||
artifacts_in_rect: function(rect) {
|
||||
return _.filter(this.active_space_artifacts, function(a) {
|
||||
return this.rects_intersecting(a.board, rect);
|
||||
return this.rects_intersecting(a, rect);
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
@ -366,15 +358,15 @@ var SpacedeckBoardArtifacts = {
|
||||
var rect = this.artifact_selection_rect();
|
||||
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) {
|
||||
max_z = max_z.board.z + 1;
|
||||
max_z = max_z.z + 1;
|
||||
} else {
|
||||
max_z = 1;
|
||||
}
|
||||
|
||||
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 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) {
|
||||
min_z = min_z.board.z - 1;
|
||||
min_z = min_z.z - 1;
|
||||
} else {
|
||||
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) {
|
||||
my_z = my_z.board.z - 1;
|
||||
my_z = my_z.z - 1;
|
||||
} else {
|
||||
my_z = 0;
|
||||
}
|
||||
@ -400,14 +392,14 @@ var SpacedeckBoardArtifacts = {
|
||||
// TODO: move all other items up in this case?
|
||||
if (min_z < 0) {
|
||||
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;
|
||||
}
|
||||
|
||||
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();
|
||||
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();
|
||||
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();
|
||||
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();
|
||||
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 cx = rect.x1 + (rect.x2-rect.x1)/2;
|
||||
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 cy = rect.y1 + (rect.y2-rect.y1)/2;
|
||||
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();
|
||||
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;
|
||||
|
||||
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();
|
||||
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;
|
||||
|
||||
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();
|
||||
if (selected.length<3) return;
|
||||
|
||||
var sorted = _.sortBy(selected, function(a) { return a.board.x });
|
||||
var startx = sorted[0].board.x + sorted[0].board.w/2;
|
||||
var stopx = _.last(sorted).board.x + _.last(sorted).board.w/2;
|
||||
var sorted = _.sortBy(selected, function(a) { return a.x });
|
||||
var startx = sorted[0].x + sorted[0].w/2;
|
||||
var stopx = _.last(sorted).x + _.last(sorted).w/2;
|
||||
var step = (stopx-startx)/(sorted.length-1);
|
||||
|
||||
for (var i=1; i<sorted.length-1; 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) {
|
||||
return { board: _.extend(a.board, {x: x}) }
|
||||
return { x: x }
|
||||
});
|
||||
}
|
||||
},
|
||||
@ -526,16 +518,16 @@ var SpacedeckBoardArtifacts = {
|
||||
var selected = this.selected_artifacts();
|
||||
if (selected.length<3) return;
|
||||
|
||||
var sorted = _.sortBy(selected, function(a) { return a.board.y });
|
||||
var starty = sorted[0].board.y + sorted[0].board.h/2;
|
||||
var stopy = _.last(sorted).board.y + _.last(sorted).board.h/2;
|
||||
var sorted = _.sortBy(selected, function(a) { return a.y });
|
||||
var starty = sorted[0].y + sorted[0].h/2;
|
||||
var stopy = _.last(sorted).y + _.last(sorted).h/2;
|
||||
var step = (stopy-starty)/(sorted.length-1);
|
||||
|
||||
for (var i=1; i<sorted.length-1; 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) {
|
||||
return { board: _.extend(a.board, {y: y}) }
|
||||
return { y: y }
|
||||
});
|
||||
}
|
||||
},
|
||||
@ -546,21 +538,21 @@ var SpacedeckBoardArtifacts = {
|
||||
var selected = this.selected_artifacts();
|
||||
if (selected.length<3) return;
|
||||
|
||||
var sorted = _.sortBy(selected, function(a) { return a.board.x });
|
||||
var startx = sorted[0].board.x;
|
||||
var stopx = _.last(sorted).board.x + _.last(sorted).board.w;
|
||||
var sorted = _.sortBy(selected, function(a) { return a.x });
|
||||
var startx = sorted[0].x;
|
||||
var stopx = _.last(sorted).x + _.last(sorted).w;
|
||||
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 prevend = startx + sorted[0].board.w;
|
||||
var prevend = startx + sorted[0].w;
|
||||
|
||||
for (var i=1; i<sorted.length-1; i++) {
|
||||
var a = sorted[i];
|
||||
var x = prevend + avgs;
|
||||
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();
|
||||
if (selected.length<3) return;
|
||||
|
||||
var sorted = _.sortBy(selected, function(a) { return a.board.y });
|
||||
var starty = sorted[0].board.y;
|
||||
var stopy = _.last(sorted).board.y + _.last(sorted).board.h;
|
||||
var sorted = _.sortBy(selected, function(a) { return a.y });
|
||||
var starty = sorted[0].y;
|
||||
var stopy = _.last(sorted).y + _.last(sorted).h;
|
||||
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 prevend = starty + sorted[0].board.h;
|
||||
var prevend = starty + sorted[0].h;
|
||||
|
||||
for (var i=1; i<sorted.length-1; i++) {
|
||||
var a = sorted[i];
|
||||
var y = prevend + avgs;
|
||||
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();
|
||||
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 miny = sorted[0].board.y;
|
||||
var minx = sorted[0].x;
|
||||
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 = [];
|
||||
|
||||
for (var i=0; i<sorted.length; i++) {
|
||||
var a = sorted[i];
|
||||
blocks.push({
|
||||
w: a.board.w,
|
||||
h: a.board.h,
|
||||
w: a.w,
|
||||
h: a.h,
|
||||
a: a
|
||||
});
|
||||
}
|
||||
@ -620,10 +612,10 @@ var SpacedeckBoardArtifacts = {
|
||||
if (block.fit) {
|
||||
var a = block.a;
|
||||
this.update_artifacts([a],function(a) {
|
||||
return { board: _.extend(a.board, {
|
||||
return {
|
||||
x: minx+block.fit.x,
|
||||
y: miny+block.fit.y
|
||||
}) }
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -170,7 +170,6 @@ var SpacedeckRoutes = {
|
||||
location.href = "/";
|
||||
} else {
|
||||
this.active_view = "account";
|
||||
this.load_subscription();
|
||||
}
|
||||
}.bind(this)
|
||||
}
|
||||
|
@ -369,8 +369,8 @@ var SpacedeckSections = {
|
||||
// canvas
|
||||
this.$watch('active_style.background_color', function (value, mutation) {
|
||||
|
||||
if (this.active_style.background_color != this.active_space.advanced.background_color) {
|
||||
this.$set("active_space.advanced.background_color",this.active_style.background_color);
|
||||
if (this.active_style.background_color != this.active_space.background_color) {
|
||||
this.$set("active_space.background_color",this.active_style.background_color);
|
||||
this.throttled_save_active_space();
|
||||
}
|
||||
|
||||
@ -448,7 +448,7 @@ var SpacedeckSections = {
|
||||
|
||||
for (var i=0; i<props.length; i++) {
|
||||
var prop = props[i];
|
||||
this.active_style[prop]=a.style[prop];
|
||||
this.active_style[prop]=a[prop];
|
||||
}
|
||||
|
||||
// defaults
|
||||
@ -457,10 +457,10 @@ var SpacedeckSections = {
|
||||
this.active_style.line_height = this.default_style.line_height;
|
||||
this.active_style.letter_spacing = this.default_style.letter_spacing;
|
||||
|
||||
this.active_style.padding_top = a.style.padding_top || 0;
|
||||
this.active_style.padding_bottom = a.style.padding_bottom || 0;
|
||||
this.active_style.padding_left = a.style.padding_left || 0;
|
||||
this.active_style.padding_right = a.style.padding_right || 0;
|
||||
this.active_style.padding_top = a.padding_top || 0;
|
||||
this.active_style.padding_bottom = a.padding_bottom || 0;
|
||||
this.active_style.padding_left = a.padding_left || 0;
|
||||
this.active_style.padding_right = a.padding_right || 0;
|
||||
|
||||
if (this.active_style.padding_top == this.active_style.padding_bottom) {
|
||||
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.margin_top = a.style.margin_top || 0;
|
||||
this.active_style.margin_bottom = a.style.margin_bottom || 0;
|
||||
this.active_style.margin_left = a.style.margin_left || 0;
|
||||
this.active_style.margin_right = a.style.margin_right || 0;
|
||||
this.active_style.margin_top = a.margin_top || 0;
|
||||
this.active_style.margin_bottom = a.margin_bottom || 0;
|
||||
this.active_style.margin_left = a.margin_left || 0;
|
||||
this.active_style.margin_right = a.margin_right || 0;
|
||||
|
||||
if (this.active_style.margin_top == this.active_style.margin_bottom) {
|
||||
this.active_style.margin_vert = this.active_style.margin_top;
|
||||
@ -758,8 +758,8 @@ var SpacedeckSections = {
|
||||
},
|
||||
|
||||
resize_minimap: function() {
|
||||
if (!this.active_space || !this.active_space.advanced) return;
|
||||
this.minimap_scale = this.active_space.advanced.width/100.0;
|
||||
if (!this.active_space) return;
|
||||
this.minimap_scale = this.active_space.width/100.0;
|
||||
},
|
||||
|
||||
handle_minimap_mouseup: function(evt) {
|
||||
@ -921,7 +921,7 @@ var SpacedeckSections = {
|
||||
|
||||
discover_zones: function() {
|
||||
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) {
|
||||
@ -1015,10 +1015,10 @@ var SpacedeckSections = {
|
||||
arts = _.filter(arts); // remove any nulls
|
||||
|
||||
return {
|
||||
x1: parseInt(_.min(arts.map(function(a){return ((!a.board || !a.board.x)?0:a.board.x)}))),
|
||||
y1: parseInt(_.min(arts.map(function(a){return ((!a.board || !a.board.y)?0:a.board.y)}))),
|
||||
x2: parseInt(_.max(arts.map(function(a){return (!a.board?0:a.board.x+a.board.w)}))),
|
||||
y2: parseInt(_.max(arts.map(function(a){return (!a.board?0:a.board.y+a.board.h)})))
|
||||
x1: parseInt(_.min(arts.map(function(a){return ((!a || !a.x)?0:a.x)}))),
|
||||
y1: parseInt(_.min(arts.map(function(a){return ((!a || !a.y)?0:a.y)}))),
|
||||
x2: parseInt(_.max(arts.map(function(a){return (!a?0:a.x+a.w)}))),
|
||||
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.scribble_selection = false;
|
||||
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.vector_points = arts[0].control_points;
|
||||
@ -1112,8 +1112,8 @@ var SpacedeckSections = {
|
||||
fixup_space_size: function() {
|
||||
if (!this.active_space) return;
|
||||
|
||||
this.active_space.advanced.width =Math.max(this.active_space.advanced.width, window.innerWidth);
|
||||
this.active_space.advanced.height=Math.max(this.active_space.advanced.height, window.innerHeight);
|
||||
this.active_space.width =Math.max(this.active_space.width, window.innerWidth);
|
||||
this.active_space.height=Math.max(this.active_space.height, window.innerHeight);
|
||||
},
|
||||
|
||||
end_transaction: function() {
|
||||
@ -1125,13 +1125,13 @@ var SpacedeckSections = {
|
||||
var er = this.enclosing_rect(this.active_space_artifacts);
|
||||
if (!er) return;
|
||||
|
||||
this.active_space.advanced.width =Math.max(er.x2+100, window.innerWidth);
|
||||
this.active_space.advanced.height=Math.max(er.y2+100, window.innerHeight);
|
||||
this.active_space.width =Math.max(er.x2+100, window.innerWidth);
|
||||
this.active_space.height=Math.max(er.y2+100, window.innerHeight);
|
||||
|
||||
if (this._last_bounds_width != this.active_space.advanced.width ||
|
||||
this._last_bounds_height != this.active_space.advanced.height) {
|
||||
this._last_bounds_width = this.active_space.advanced.width;
|
||||
this._last_bounds_height = this.active_space.advanced.height;
|
||||
if (this._last_bounds_width != this.active_space.width ||
|
||||
this._last_bounds_height != this.active_space.height) {
|
||||
this._last_bounds_width = this.active_space.width;
|
||||
this._last_bounds_height = this.active_space.height;
|
||||
|
||||
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
|
||||
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;
|
||||
@ -1329,7 +1329,7 @@ var SpacedeckSections = {
|
||||
this.update_selected_artifacts(function(a) {
|
||||
var c = {};
|
||||
|
||||
if (c[prop] != val) {
|
||||
if (a[prop] != val) {
|
||||
//console.log("set_artifact_prop: ",c,val);
|
||||
c[prop]=val;
|
||||
return c;
|
||||
@ -1343,11 +1343,11 @@ var SpacedeckSections = {
|
||||
this.begin_transaction();
|
||||
|
||||
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);
|
||||
c.style[prop]=val;
|
||||
c[prop]=val;
|
||||
return c;
|
||||
}
|
||||
|
||||
@ -1419,7 +1419,7 @@ var SpacedeckSections = {
|
||||
if (this.selected_artifacts().length!=1 && this.opened_dialog!="background") return;
|
||||
|
||||
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 {
|
||||
if (!this.active_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) {
|
||||
return {
|
||||
board: _.extend(a.board, {
|
||||
x: a.board.x+dx,
|
||||
y: a.board.y+dy
|
||||
})
|
||||
x: a.x+dx,
|
||||
y: a.y+dy
|
||||
};
|
||||
});
|
||||
},
|
||||
@ -1489,7 +1487,7 @@ var SpacedeckSections = {
|
||||
/* -------------------------------------------------------------------- */
|
||||
|
||||
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>999) z=999;
|
||||
return z;
|
||||
@ -1574,20 +1572,18 @@ var SpacedeckSections = {
|
||||
payload_thumbnail_web_uri: url || null,
|
||||
space_id: space._id,
|
||||
|
||||
style: {
|
||||
order: this.active_space_artifacts.length+1,
|
||||
valign: "middle",
|
||||
align: "center"
|
||||
//fill_color: "#f8f8f8"
|
||||
}
|
||||
};
|
||||
|
||||
if (mimes[item_type] == "text/html") {
|
||||
new_item.style.padding_left = 10;
|
||||
new_item.style.padding_top = 10;
|
||||
new_item.style.padding_right = 10;
|
||||
new_item.style.padding_bottom = 10;
|
||||
new_item.style.fill_color = "rgba(255,255,255,1)";
|
||||
new_item.padding_left = 10;
|
||||
new_item.padding_top = 10;
|
||||
new_item.padding_right = 10;
|
||||
new_item.padding_bottom = 10;
|
||||
new_item.fill_color = "rgba(255,255,255,1)";
|
||||
new_item.description = "<p>Text</p>";
|
||||
}
|
||||
|
||||
@ -1600,13 +1596,11 @@ var SpacedeckSections = {
|
||||
z = point.z;
|
||||
}
|
||||
|
||||
new_item.board = {
|
||||
x: parseInt(point.x),
|
||||
y: parseInt(point.y),
|
||||
w: w,
|
||||
h: h,
|
||||
z: z
|
||||
};
|
||||
new_item.x = parseInt(point.x);
|
||||
new_item.y = parseInt(point.y);
|
||||
new_item.z = z;
|
||||
new_item.w = w;
|
||||
new_item.h = h;
|
||||
|
||||
if (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++) {
|
||||
if (new_zones[i]) {
|
||||
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]);
|
||||
}
|
||||
}
|
||||
@ -1679,7 +1673,7 @@ var SpacedeckSections = {
|
||||
for (var i=0; i<new_zones.length; i++) {
|
||||
if (new_zones[i]) {
|
||||
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]);
|
||||
}
|
||||
}
|
||||
@ -1695,17 +1689,13 @@ var SpacedeckSections = {
|
||||
space_id: this.active_space._id,
|
||||
mime: "x-spacedeck/zone",
|
||||
description: "Zone "+(this.zones.length+1),
|
||||
board: {
|
||||
x: point.x,
|
||||
y: point.y,
|
||||
w: w,
|
||||
h: h,
|
||||
z: 0
|
||||
},
|
||||
style: {
|
||||
z: 0,
|
||||
valign: "middle",
|
||||
align: "center"
|
||||
}
|
||||
};
|
||||
|
||||
if (this.guest_nickname) {
|
||||
@ -1735,14 +1725,11 @@ var SpacedeckSections = {
|
||||
space_id: this.active_space._id,
|
||||
mime: "x-spacedeck/shape",
|
||||
description: "Text",
|
||||
board: {
|
||||
x: point.x,
|
||||
y: point.y,
|
||||
z: point.z,
|
||||
w: w,
|
||||
h: h
|
||||
},
|
||||
style: {
|
||||
h: h,
|
||||
stroke_color: "#ffffff",
|
||||
text_color: "#ffffff",
|
||||
stroke: 0,
|
||||
@ -1750,7 +1737,6 @@ var SpacedeckSections = {
|
||||
shape: shape_type,
|
||||
valign: "middle",
|
||||
align: "center"
|
||||
}
|
||||
};
|
||||
|
||||
if (this.guest_nickname) {
|
||||
@ -1829,18 +1815,14 @@ var SpacedeckSections = {
|
||||
state: "uploading",
|
||||
payload_thumbnail_medium_uri: null,
|
||||
payload_thumbnail_web_uri: null,
|
||||
board: {
|
||||
x: point.x,
|
||||
y: point.y,
|
||||
w: w,
|
||||
h: h,
|
||||
z: point.z
|
||||
},
|
||||
style: {
|
||||
z: point.z,
|
||||
order: this.active_space_artifacts.length+1,
|
||||
fill_color: fill
|
||||
}
|
||||
}
|
||||
|
||||
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_alternatives = updated_a.payload_alternatives;
|
||||
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;
|
||||
this.update_board_artifact_viewmodel(a);
|
||||
|
||||
@ -2002,26 +1988,26 @@ var SpacedeckSections = {
|
||||
clear_formatting_walk: function(el,cmd,arg1,arg2) {
|
||||
if (el && el.style) {
|
||||
if (cmd == "preciseFontSize") {
|
||||
el.style.fontSize = null;
|
||||
el.fontSize = null;
|
||||
} else if (cmd == "letterSpacing") {
|
||||
el.style.letterSpacing = null;
|
||||
el.letterSpacing = null;
|
||||
} else if (cmd == "lineHeight") {
|
||||
el.style.lineHeight = null;
|
||||
el.lineHeight = null;
|
||||
} else if (cmd == "fontName") {
|
||||
el.style.fontFamily = null;
|
||||
el.fontFamily = null;
|
||||
} else if (cmd == "fontWeight") {
|
||||
el.style.fontWeight = null;
|
||||
el.style.fontStyle = null;
|
||||
el.fontWeight = null;
|
||||
el.fontStyle = null;
|
||||
} else if (cmd == "bold") {
|
||||
el.style.fontWeight = null;
|
||||
el.fontWeight = null;
|
||||
} else if (cmd == "italic") {
|
||||
el.style.fontStyle = null;
|
||||
el.fontStyle = null;
|
||||
} else if (cmd == "underline") {
|
||||
el.style.textDecoration = null;
|
||||
el.textDecoration = null;
|
||||
} else if (cmd == "strikeThrough") {
|
||||
el.style.textDecoration = null;
|
||||
el.textDecoration = null;
|
||||
} else if (cmd == "forecolor") {
|
||||
el.style.color = null;
|
||||
el.color = null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -2108,6 +2094,9 @@ var SpacedeckSections = {
|
||||
|
||||
if (a.description!=dom.innerHTML) {
|
||||
a.description = dom.innerHTML;
|
||||
|
||||
console.log("new DOM:",dom.innerHTML);
|
||||
|
||||
this.update_board_artifact_viewmodel(a);
|
||||
this.queue_artifact_for_save(a);
|
||||
|
||||
@ -2141,10 +2130,7 @@ var SpacedeckSections = {
|
||||
|
||||
remove_link_from_selected_artifacts: function() {
|
||||
this.update_selected_artifacts(function(a) {
|
||||
var meta = a.meta || {};
|
||||
delete meta.link_uri;
|
||||
|
||||
return {meta: meta};
|
||||
return {link_uri: ""};
|
||||
});
|
||||
},
|
||||
|
||||
@ -2160,9 +2146,7 @@ var SpacedeckSections = {
|
||||
var insert_link_url = prompt("URL:",def);
|
||||
|
||||
this.update_selected_artifacts(function(a) {
|
||||
var meta = a.meta || {};
|
||||
meta.link_uri = insert_link_url;
|
||||
var update = {meta: meta};
|
||||
var update = {link_uri: insert_link_url};
|
||||
|
||||
if (a.payload_uri && a.payload_uri.match("webgrabber")) {
|
||||
var enc_uri = encodeURIComponent(btoa(insert_link_url));
|
||||
@ -2185,11 +2169,10 @@ var SpacedeckSections = {
|
||||
delete copy["$index"];
|
||||
delete copy["_id"];
|
||||
|
||||
if (dx) copy.board.x += dx;
|
||||
if (dy) copy.board.y += dy;
|
||||
if (dx) copy.x += dx;
|
||||
if (dy) copy.y += dy;
|
||||
|
||||
if (!copy.style) copy.style = {};
|
||||
copy.style.order = this.active_space_artifacts.length+1;
|
||||
copy.order = this.active_space_artifacts.length+1;
|
||||
|
||||
if (this.guest_nickname) {
|
||||
copy.editor_name = this.guest_nickname;
|
||||
@ -2334,16 +2317,16 @@ var SpacedeckSections = {
|
||||
if (parsed[i].mime) {
|
||||
var z = this.highest_z()+1;
|
||||
if (parsed.length==1) {
|
||||
var w = parsed[i].board.w;
|
||||
var h = parsed[i].board.h;
|
||||
var w = parsed[i].w;
|
||||
var h = parsed[i].h;
|
||||
var point = this.find_place_for_item(w,h);
|
||||
parsed[i].board.x = point.x;
|
||||
parsed[i].board.y = point.y;
|
||||
parsed[i].board.z = point.z;
|
||||
parsed[i].x = point.x;
|
||||
parsed[i].y = point.y;
|
||||
parsed[i].z = point.z;
|
||||
} else {
|
||||
parsed[i].board.x = parsed[i].board.x+50;
|
||||
parsed[i].board.y = parsed[i].board.y+50;
|
||||
parsed[i].board.y = parsed[i].board.z+z;
|
||||
parsed[i].x = parsed[i].x+50;
|
||||
parsed[i].y = parsed[i].y+50;
|
||||
parsed[i].y = parsed[i].z+z;
|
||||
}
|
||||
this.clone_artifact(parsed[i], 0,0, function(a) {
|
||||
this.multi_select([a]);
|
||||
@ -2373,13 +2356,11 @@ var SpacedeckSections = {
|
||||
var h = 300;
|
||||
var point = this.find_place_for_item(w,h);
|
||||
|
||||
new_item.board = {
|
||||
x: point.x,
|
||||
y: point.y,
|
||||
w: w,
|
||||
h: h,
|
||||
z: point.z
|
||||
};
|
||||
new_item.x = point.x;
|
||||
new_item.y = point.y;
|
||||
new_item.w = w;
|
||||
new_item.h = h;
|
||||
new_item.z = point.z;
|
||||
|
||||
if (this.guest_nickname) {
|
||||
new_item.editor_name = this.guest_nickname;
|
||||
@ -2402,17 +2383,13 @@ var SpacedeckSections = {
|
||||
mime: "image/png",
|
||||
description: url,
|
||||
state: "uploading",
|
||||
board: {
|
||||
x: point.x,
|
||||
y: point.y,
|
||||
w: 200,
|
||||
h: 200,
|
||||
z: z
|
||||
},
|
||||
style: {
|
||||
z: z,
|
||||
order: this.active_space_artifacts.length
|
||||
}
|
||||
}
|
||||
|
||||
var metadata = parse_link(url)
|
||||
|
||||
@ -2473,16 +2450,12 @@ var SpacedeckSections = {
|
||||
payload_thumbnail_medium_uri: metadata.thumbnail_url,
|
||||
payload_thumbnail_web_uri: metadata.thumbnail_url,
|
||||
state: "idle",
|
||||
meta: {
|
||||
title: metadata.title,
|
||||
link_uri: metadata.url || url
|
||||
},
|
||||
board: {
|
||||
link_uri: metadata.url || url,
|
||||
x: point.x - w/2,
|
||||
y: point.y - h/2,
|
||||
w: w,
|
||||
h: h
|
||||
}
|
||||
});
|
||||
|
||||
if (this.guest_nickname) {
|
||||
@ -2591,7 +2564,7 @@ var SpacedeckSections = {
|
||||
},
|
||||
|
||||
remove_section_background: function() {
|
||||
this.active_space.advanced.background_uri = null;
|
||||
this.active_space.background_uri = null;
|
||||
save_space(this.active_space);
|
||||
},
|
||||
|
||||
@ -2652,8 +2625,8 @@ var SpacedeckSections = {
|
||||
|
||||
this.bounds_zoom = this.viewport_zoom;
|
||||
|
||||
var eff_w = this.active_space.advanced.width*this.viewport_zoom;
|
||||
var eff_h = this.active_space.advanced.height*this.viewport_zoom;
|
||||
var eff_w = this.active_space.width*this.viewport_zoom;
|
||||
var eff_h = this.active_space.height*this.viewport_zoom;
|
||||
|
||||
if (window.innerWidth>eff_w) {
|
||||
// horizontal centering
|
||||
@ -2846,8 +2819,8 @@ var SpacedeckSections = {
|
||||
|
||||
var el = $("#space")[0];
|
||||
|
||||
var eff_w = this.active_space.advanced.width*this.viewport_zoom;
|
||||
var eff_h = this.active_space.advanced.height*this.viewport_zoom;
|
||||
var eff_w = this.active_space.width*this.viewport_zoom;
|
||||
var eff_h = this.active_space.height*this.viewport_zoom;
|
||||
|
||||
var sx = el.scrollLeft;
|
||||
var sy = el.scrollTop;
|
||||
@ -2980,9 +2953,9 @@ var SpacedeckSections = {
|
||||
|
||||
var w = 300;
|
||||
var h = 200;
|
||||
if (parsed.board && parsed.board.w && parsed.board.h) {
|
||||
w = parsed.board.w;
|
||||
h = parsed.board.h;
|
||||
if (parsed.board && parsed.w && parsed.h) {
|
||||
w = parsed.w;
|
||||
h = parsed.h;
|
||||
}
|
||||
|
||||
var point = this.cursor_point_to_space(evt);
|
||||
|
@ -283,9 +283,9 @@ var SpacedeckSpaces = {
|
||||
|
||||
this.discover_zones();
|
||||
|
||||
window.setTimeout(function() {
|
||||
this.zoom_to_fit();
|
||||
}.bind(this),10);
|
||||
//window.setTimeout(function() {
|
||||
// this.zoom_to_fit();
|
||||
//}.bind(this),10);
|
||||
|
||||
if (on_success) {
|
||||
on_success();
|
||||
@ -640,13 +640,10 @@ var SpacedeckSpaces = {
|
||||
this.download_space_as_pdf(this.active_space);
|
||||
} else if (e == "ZIP") {
|
||||
this.download_space_as_zip(this.active_space);
|
||||
}else if (e == "TXT"){
|
||||
this.download_space_as_list(this.active_space);
|
||||
}
|
||||
}.bind(this), {
|
||||
button_1: "PDF",
|
||||
button_2: "ZIP",
|
||||
button_3: "TXT",
|
||||
button_cancel:__("cancel")
|
||||
});
|
||||
|
||||
|
@ -15,7 +15,8 @@ SpacedeckUsers = {
|
||||
account_remove_error: null,
|
||||
loading_user: false,
|
||||
password_reset_confirm_error: "",
|
||||
password_reset_error: ""
|
||||
password_reset_error: "",
|
||||
|
||||
},
|
||||
methods:{
|
||||
load_user: function(on_success, on_error) {
|
||||
@ -29,6 +30,12 @@ SpacedeckUsers = {
|
||||
if (on_success) {
|
||||
on_success(user);
|
||||
}
|
||||
|
||||
// see spacedeck_account.js
|
||||
load_importables(this.user, function(files) {
|
||||
this.importables = files;
|
||||
}.bind(this));
|
||||
|
||||
}.bind(this), function() {
|
||||
// error
|
||||
this.loading_user = false;
|
||||
@ -40,18 +47,6 @@ SpacedeckUsers = {
|
||||
}.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) {
|
||||
if(!window.socket_auth || window.socket_auth == '' || window.socket_auth == 'null') {
|
||||
window.socket_auth = session_token;
|
||||
@ -169,7 +164,6 @@ SpacedeckUsers = {
|
||||
},
|
||||
|
||||
password_reset_submit: function(evt, email) {
|
||||
|
||||
if (evt) {
|
||||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
@ -203,7 +197,6 @@ SpacedeckUsers = {
|
||||
},
|
||||
|
||||
password_reset_confirm: function(evt, password, password_confirmation) {
|
||||
|
||||
if (evt) {
|
||||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
|
@ -158,7 +158,7 @@ function boot_spacedeck() {
|
||||
});
|
||||
}
|
||||
|
||||
$(document).ready(function(){
|
||||
document.addEventListener("DOMContentLoaded",function() {
|
||||
window.smoke = smoke;
|
||||
window.alert = smoke.alert;
|
||||
|
||||
|
@ -331,7 +331,7 @@ function setup_whiteboard_directives() {
|
||||
var $scope = this.vm.$root;
|
||||
|
||||
return _.filter($scope.active_space_artifacts, function(a) {
|
||||
return this.rects_intersecting(a.board, rect);
|
||||
return this.rects_intersecting(a, rect);
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
@ -439,15 +439,15 @@ function setup_whiteboard_directives() {
|
||||
|
||||
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 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 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 (xd2<xd1) {
|
||||
@ -469,10 +469,10 @@ function setup_whiteboard_directives() {
|
||||
|
||||
if (snap_middle) {
|
||||
var xd = xd3;
|
||||
var sx = r[0].x+a.board.w/2;
|
||||
var sx = r[0].x+a.w/2;
|
||||
|
||||
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]];
|
||||
@ -531,18 +531,14 @@ function setup_whiteboard_directives() {
|
||||
mime: "x-spacedeck/vector",
|
||||
description: "",
|
||||
control_points: [{dx:0,dy:0}],
|
||||
board: {
|
||||
x: point.x,
|
||||
y: point.y,
|
||||
z: z,
|
||||
w: 64,
|
||||
h: 64
|
||||
},
|
||||
style: {
|
||||
h: 64,
|
||||
stroke_color: "#000000",
|
||||
stroke: 2,
|
||||
shape: "scribble"
|
||||
}
|
||||
};
|
||||
|
||||
$scope.save_artifact(a, function(saved_a) {
|
||||
@ -572,18 +568,14 @@ function setup_whiteboard_directives() {
|
||||
mime: "x-spacedeck/vector",
|
||||
description: "",
|
||||
control_points: [{dx:0,dy:0},{dx:0,dy:0},{dx:0,dy:0}],
|
||||
board: {
|
||||
x: point.x,
|
||||
y: point.y,
|
||||
z: z,
|
||||
w: 64,
|
||||
h: 64
|
||||
},
|
||||
style: {
|
||||
h: 64,
|
||||
stroke_color: "#000000",
|
||||
stroke: 2,
|
||||
shape: "arrow"
|
||||
}
|
||||
};
|
||||
|
||||
$scope.save_artifact(a, function(saved_a) {
|
||||
@ -612,18 +604,14 @@ function setup_whiteboard_directives() {
|
||||
mime: "x-spacedeck/vector",
|
||||
description: "",
|
||||
control_points: [{dx:0,dy:0},{dx:0,dy:0}],
|
||||
board: {
|
||||
x: point.x,
|
||||
y: point.y,
|
||||
z: z,
|
||||
w: 64,
|
||||
h: 64
|
||||
},
|
||||
style: {
|
||||
h: 64,
|
||||
stroke_color: "#000000",
|
||||
stroke: 2,
|
||||
shape: "line"
|
||||
}
|
||||
};
|
||||
|
||||
$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]))) {
|
||||
// some types of artifact need a minimum size
|
||||
if (ars[i].board.w<10) {
|
||||
ars[i].board.w = 10;
|
||||
if (ars[i].w<10) {
|
||||
ars[i].w = 10;
|
||||
}
|
||||
if (ars[i].board.h<10) {
|
||||
ars[i].board.h = 10;
|
||||
if (ars[i].h<10) {
|
||||
ars[i].h = 10;
|
||||
}
|
||||
}
|
||||
|
||||
@ -827,10 +815,8 @@ function setup_whiteboard_directives() {
|
||||
|
||||
if (old_a) {
|
||||
return {
|
||||
board: _.extend(a.board, {
|
||||
x: old_a.board.x + dx - snap_dx,
|
||||
y: old_a.board.y + dy - snap_dy
|
||||
})
|
||||
x: old_a.x + dx - snap_dx,
|
||||
y: old_a.y + dy - snap_dy
|
||||
};
|
||||
} else {
|
||||
// deleted?
|
||||
@ -865,26 +851,24 @@ function setup_whiteboard_directives() {
|
||||
|
||||
var scale_x = lead_x ? (moved_x)/lead_x : 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) {
|
||||
var old_a = $scope.find_artifact_before_transaction(a);
|
||||
|
||||
var x1 = origin_x + ((old_a.board.x - origin_x) * scale_x);
|
||||
var y1 = origin_y + ((old_a.board.y - origin_y) * scale_y);
|
||||
var x2 = origin_x + (((old_a.board.x + old_a.board.w) - origin_x) * scale_x);
|
||||
var y2 = origin_y + (((old_a.board.y + old_a.board.h) - origin_y) * scale_y);
|
||||
var x1 = origin_x + ((old_a.x - origin_x) * scale_x);
|
||||
var y1 = origin_y + ((old_a.y - origin_y) * scale_y);
|
||||
var x2 = origin_x + (((old_a.x + old_a.w) - origin_x) * scale_x);
|
||||
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 (y1>y2) { var t = y1; y1 = y2; y2 = t; }
|
||||
|
||||
return {
|
||||
board: _.extend(a.board, {
|
||||
x: x1,
|
||||
y: y1,
|
||||
w: x2 - x1,
|
||||
h: y2 - y1
|
||||
})
|
||||
};
|
||||
}.bind(this));
|
||||
|
||||
@ -902,18 +886,17 @@ function setup_whiteboard_directives() {
|
||||
var old_a = $scope.find_artifact_before_transaction(a);
|
||||
|
||||
var control_points = _.cloneDeep(old_a.control_points);
|
||||
var board = _.clone(old_a.board);
|
||||
var cp = control_points[$scope.selected_control_point_idx];
|
||||
|
||||
var snapped = _this.snap_point(board.x+cp.dx+dx, board.y+cp.dy+dy);
|
||||
dx = snapped.snapx[1]-(board.x+cp.dx);
|
||||
dy = snapped.snapy[1]-(board.y+cp.dy);
|
||||
var snapped = _this.snap_point(old_a.x+cp.dx+dx, old_a.y+cp.dy+dy);
|
||||
dx = snapped.snapx[1]-(old_a.x+cp.dx);
|
||||
dy = snapped.snapy[1]-(old_a.y+cp.dy);
|
||||
|
||||
cp.dx += dx;
|
||||
cp.dy += dy;
|
||||
|
||||
// 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].dy += dy/2; */
|
||||
|
||||
@ -921,7 +904,7 @@ function setup_whiteboard_directives() {
|
||||
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") {
|
||||
@ -930,16 +913,14 @@ function setup_whiteboard_directives() {
|
||||
var old_a = a;
|
||||
|
||||
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});
|
||||
|
||||
control_points.push({
|
||||
dx: offset.x-board.x,
|
||||
dy: offset.y-board.y
|
||||
dx: offset.x-old_a.x,
|
||||
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));
|
||||
|
||||
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 y1 = _.min(control_points,"dy").dy;
|
||||
var x2 = _.max(control_points,"dx").dx;
|
||||
@ -981,19 +962,15 @@ function setup_whiteboard_directives() {
|
||||
var bshiftx = 0;
|
||||
var bshifty = 0;
|
||||
|
||||
if (board.w < 0) bshiftx = -board.w;
|
||||
if (board.h < 0) bshifty = -board.h;
|
||||
|
||||
var shifted_board = {
|
||||
x: board.x + bshiftx - shiftx,
|
||||
y: board.y + bshifty - shifty,
|
||||
w: w,
|
||||
h: h,
|
||||
z: board.z
|
||||
};
|
||||
if (artifact.w < 0) bshiftx = -artifact.w;
|
||||
if (artifact.h < 0) bshifty = -artifact.h;
|
||||
|
||||
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
|
||||
};
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ function vec2_angle(v) {
|
||||
}
|
||||
|
||||
function render_vector_drawing(a, padding) {
|
||||
var shape = a.style.shape || "";
|
||||
var shape = a.shape || "";
|
||||
var path = [];
|
||||
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 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>";
|
||||
var svg = tip + "<path d='" + d + "' style='stroke-width:" + a.style.stroke + ";' marker-end='url(#ae" + markerId + ")'/>";
|
||||
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.stroke + ";' marker-end='url(#ae" + markerId + ")'/>";
|
||||
|
||||
return svg;
|
||||
}
|
||||
@ -237,11 +237,11 @@ function render_vector_rect(xradius,yradius,offset) {
|
||||
}
|
||||
|
||||
function render_vector_shape(a) {
|
||||
var stroke = parseInt(a.style.stroke) + 4;
|
||||
var stroke = parseInt(a.stroke) + 4;
|
||||
var offset = stroke / 2;
|
||||
|
||||
var xr = (a.board.w-stroke) / 2;
|
||||
var yr = (a.board.h-stroke) / 2;
|
||||
var xr = (a.w-stroke) / 2;
|
||||
var yr = (a.h-stroke) / 2;
|
||||
|
||||
var shape_renderers = {
|
||||
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); },
|
||||
}
|
||||
|
||||
var render_func = shape_renderers[a.style.shape];
|
||||
var render_func = shape_renderers[a.shape];
|
||||
|
||||
if (!render_func) return "";
|
||||
|
||||
|
@ -1,57 +1,41 @@
|
||||
"use strict";
|
||||
|
||||
var config = require('config');
|
||||
require('../../models/schema');
|
||||
|
||||
var fs = require('fs');
|
||||
var _ = require("underscore");
|
||||
var mongoose = require("mongoose");
|
||||
var async = require('async');
|
||||
var archiver = require('archiver');
|
||||
var request = require('request');
|
||||
var url = require("url");
|
||||
var path = require("path");
|
||||
var crypto = require('crypto');
|
||||
var qr = require('qr-image');
|
||||
var glob = require('glob');
|
||||
var gm = require('gm');
|
||||
|
||||
var express = require('express');
|
||||
var router = express.Router();
|
||||
|
||||
var userMapping = { '_id': 1, 'nickname': 1, 'email': 1};
|
||||
var spaceMapping = { '_id': 1, name: 1};
|
||||
const db = require('../../models/db');
|
||||
const Sequelize = require('sequelize');
|
||||
const Op = Sequelize.Op;
|
||||
const uuidv4 = require('uuid/v4');
|
||||
|
||||
router.get('/:membership_id/accept', function(req, res, next) {
|
||||
if (req.user) {
|
||||
Membership.findOne({
|
||||
db.Membership.findOne({where:{
|
||||
_id: req.params.membership_id,
|
||||
state: "pending",
|
||||
code: req.query.code,
|
||||
user: { "$exists": false }
|
||||
}).populate('space').exec((err,mem) => {
|
||||
if (err) res.sendStatus(400);
|
||||
else {
|
||||
code: req.query.code
|
||||
}, include: ['space']}).then((mem) => {
|
||||
if (mem) {
|
||||
if (!mem.user) {
|
||||
mem.code = null;
|
||||
mem.state = "active";
|
||||
mem.user = req.user;
|
||||
mem.user_id = req.user._id;
|
||||
|
||||
mem.save(function(err){
|
||||
if (err) res.status(400).json(err);
|
||||
else {
|
||||
console.log(mem);
|
||||
mem.save().then(function() {
|
||||
res.status(200).json(mem);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
res.status(400).json({"error": "already_used"});
|
||||
res.status(200).json(mem);
|
||||
}
|
||||
} else {
|
||||
res.status(404).json({"error": "not found"});
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
res.sendStatus(403);
|
||||
|
@ -1,10 +1,10 @@
|
||||
"use strict";
|
||||
|
||||
var config = require('config');
|
||||
require('../../models/schema');
|
||||
const db = require('../../models/db');
|
||||
|
||||
var bcrypt = require('bcryptjs');
|
||||
var crypo = require('crypto');
|
||||
var crypto = require('crypto');
|
||||
var URL = require('url').URL;
|
||||
|
||||
var express = require('express');
|
||||
@ -12,39 +12,42 @@ var router = express.Router();
|
||||
|
||||
router.post('/', function(req, res) {
|
||||
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 password = req.body["password"];
|
||||
|
||||
User.find({email: email, account_type: "email"}, (function (err, users) {
|
||||
if (err) {
|
||||
res.status(400).json({"error":"session.users"});
|
||||
} else {
|
||||
|
||||
if (users.length == 1) {
|
||||
var user = users[0];
|
||||
db.User.findOne({where: {email: email}})
|
||||
.error(err => {
|
||||
res.sendStatus(404);
|
||||
//res.status(400).json({"error":"session.users"});
|
||||
})
|
||||
.then(user => {
|
||||
console.log("!!! user: ",user.password_hash);
|
||||
|
||||
if (bcrypt.compareSync(password, user.password_hash)) {
|
||||
crypo.randomBytes(48, function(ex, buf) {
|
||||
crypto.randomBytes(48, function(ex, buf) {
|
||||
var token = buf.toString('hex');
|
||||
console.log("!!! token: ",token);
|
||||
|
||||
var session = {
|
||||
user_id: user._id,
|
||||
token: token,
|
||||
ip: req.ip,
|
||||
device: "web",
|
||||
created_at: new Date()
|
||||
};
|
||||
|
||||
if (!user.sessions)
|
||||
user.sessions = [];
|
||||
|
||||
user.sessions.push(session);
|
||||
|
||||
user.save(function(err, result) {
|
||||
if (err) console.error("Error saving user:",err);
|
||||
|
||||
db.Session.create(session)
|
||||
.error(err => {
|
||||
console.error("Error creating Session:",err);
|
||||
res.sendStatus(500);
|
||||
})
|
||||
.then(() => {
|
||||
var domain = (process.env.NODE_ENV == "production") ? new URL(config.get('endpoint')).hostname : "localhost";
|
||||
|
||||
res.cookie('sdsession', token, { domain: domain, httpOnly: true });
|
||||
res.status(201).json(session);
|
||||
});
|
||||
@ -52,28 +55,21 @@ router.post('/', function(req, res) {
|
||||
} else {
|
||||
res.sendStatus(403);
|
||||
}
|
||||
} else {
|
||||
res.sendStatus(404);
|
||||
}
|
||||
}
|
||||
}));
|
||||
} else {
|
||||
res.status(400).json({});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
router.delete('/current', function(req, res, next) {
|
||||
if (req.user) {
|
||||
var user = req.user;
|
||||
/*var user = req.user;
|
||||
var newSessions = user.sessions.filter( function(session){
|
||||
return session.token != req.token;
|
||||
});
|
||||
user.sessions = newSessions;
|
||||
user.save(function(err, result) {
|
||||
});*/
|
||||
//user.sessions = newSessions;
|
||||
//user.save(function(err, result) {
|
||||
var domain = new URL(config.get('endpoint')).hostname;
|
||||
res.clearCookie('sdsession', { domain: domain });
|
||||
res.sendStatus(204);
|
||||
});
|
||||
//});
|
||||
} else {
|
||||
res.sendStatus(404);
|
||||
}
|
||||
|
@ -1,7 +1,12 @@
|
||||
"use strict";
|
||||
|
||||
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 redis = require('../../helpers/redis');
|
||||
@ -9,13 +14,11 @@ var redis = require('../../helpers/redis');
|
||||
var async = require('async');
|
||||
var fs = require('fs');
|
||||
var _ = require("underscore");
|
||||
var mongoose = require("mongoose");
|
||||
var archiver = require('archiver');
|
||||
var request = require('request');
|
||||
var url = require("url");
|
||||
var path = require("path");
|
||||
var crypto = require('crypto');
|
||||
var qr = require('qr-image');
|
||||
var glob = require('glob');
|
||||
var gm = require('gm');
|
||||
|
||||
@ -46,15 +49,24 @@ var roleMapping = {
|
||||
// ARTIFACTS
|
||||
|
||||
router.get('/', (req, res) => {
|
||||
Artifact.find({
|
||||
db.Artifact.findAll({where: {
|
||||
space_id: req.space._id
|
||||
}).exec((err, artifacts) => {
|
||||
}}).then(artifacts => {
|
||||
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) {
|
||||
User.findOne({
|
||||
// FIXME JOIN
|
||||
/*User.findOne({where: {
|
||||
"_id": a.user_id
|
||||
}).select({
|
||||
}}).select({
|
||||
"_id": 1,
|
||||
"nickname": 1,
|
||||
"email": 1
|
||||
@ -63,7 +75,8 @@ router.get('/', (req, res) => {
|
||||
a['user'] = user.toObject();
|
||||
}
|
||||
cb(err, a);
|
||||
});
|
||||
});*/
|
||||
cb(null, a);
|
||||
} else {
|
||||
cb(null, a);
|
||||
}
|
||||
@ -81,9 +94,8 @@ router.post('/', function(req, res, next) {
|
||||
|
||||
attrs['space_id'] = req.space._id;
|
||||
|
||||
var artifact = new Artifact(attrs);
|
||||
|
||||
artifact.created_from_ip = req['real_ip'];
|
||||
var artifact = attrs;
|
||||
artifact._id = uuidv4();
|
||||
|
||||
if (req.user) {
|
||||
artifact.user_id = req.user._id;
|
||||
@ -92,23 +104,18 @@ router.post('/', function(req, res, next) {
|
||||
artifact.last_update_editor_name = req.editor_name;
|
||||
}
|
||||
|
||||
if (req.spaceRole == "editor" || req.spaceRole == "admin") {
|
||||
artifact.save(function(err) {
|
||||
if (err) res.status(400).json(err);
|
||||
else {
|
||||
Space.update({
|
||||
_id: req.space._id
|
||||
}, {
|
||||
"$set": {
|
||||
updated_at: new Date()
|
||||
}
|
||||
});
|
||||
db.packArtifact(artifact);
|
||||
|
||||
if (req.spaceRole == "editor" || req.spaceRole == "admin") {
|
||||
db.Artifact.create(artifact).then(() => {
|
||||
//if (err) res.status(400).json(err);
|
||||
db.unpackArtifact(artifact);
|
||||
db.Space.update({ updated_at: new Date() }, {where: {_id: req.space._id}});
|
||||
res.distributeCreate("Artifact", artifact);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
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 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 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) {
|
||||
if (error) res.status(400).json(error);
|
||||
else {
|
||||
Space.update({
|
||||
_id: req.space._id
|
||||
}, {
|
||||
"$set": {
|
||||
updated_at: new Date()
|
||||
}
|
||||
});
|
||||
db.Space.update({ updated_at: new Date() }, {where: {_id: req.space._id}});
|
||||
res.distributeUpdate("Artifact", artifact);
|
||||
}
|
||||
}, progress_callback);
|
||||
@ -162,41 +164,22 @@ router.put('/:artifact_id', function(req, res, next) {
|
||||
newAttr.last_update_editor_name = req.editor_name;
|
||||
}
|
||||
|
||||
Artifact.findOneAndUpdate({
|
||||
db.packArtifact(newAttr);
|
||||
|
||||
db.Artifact.update(newAttr, { where: {
|
||||
"_id": a._id
|
||||
}, {
|
||||
"$set": newAttr
|
||||
}, {
|
||||
"new": true
|
||||
}, 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);
|
||||
}
|
||||
}}).then(rows => {
|
||||
db.unpackArtifact(newAttr);
|
||||
db.Space.update({ updated_at: new Date() }, {where: {_id: req.space._id} });
|
||||
res.distributeUpdate("Artifact", newAttr);
|
||||
});
|
||||
});
|
||||
|
||||
router.delete('/:artifact_id', function(req, res, next) {
|
||||
var artifact = req.artifact;
|
||||
artifact.remove(function(err) {
|
||||
if (err) res.status(400).json(err);
|
||||
else {
|
||||
Space.update({
|
||||
_id: req.space._id
|
||||
}, {
|
||||
"$set": {
|
||||
updated_at: new Date()
|
||||
}
|
||||
});
|
||||
db.Artifact.destroy({where: { "_id": artifact._id}}).then(() => {
|
||||
db.Space.update({ updated_at: new Date() }, {where: {_id: req.space._id} });
|
||||
res.distributeDelete("Artifact", artifact);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -1,17 +1,15 @@
|
||||
"use strict";
|
||||
|
||||
var config = require('config');
|
||||
require('../../models/schema');
|
||||
require('../../models/db');
|
||||
|
||||
var async = require('async');
|
||||
var fs = require('fs');
|
||||
var _ = require("underscore");
|
||||
var mongoose = require("mongoose");
|
||||
var request = require('request');
|
||||
var url = require("url");
|
||||
var path = require("path");
|
||||
var crypto = require('crypto');
|
||||
var qr = require('qr-image');
|
||||
var glob = require('glob');
|
||||
var gm = require('gm');
|
||||
|
||||
@ -40,6 +38,12 @@ var roleMapping = {
|
||||
};
|
||||
|
||||
router.get('/', function(req, res, next) {
|
||||
|
||||
res.status(200).json([]);
|
||||
return;
|
||||
|
||||
// FIXME TODO
|
||||
|
||||
var showActionForSpaces = function(err, spaceIds) {
|
||||
var userMapping = {
|
||||
'_id': 1,
|
||||
|
@ -1,8 +1,7 @@
|
||||
"use strict";
|
||||
var config = require('config');
|
||||
require('../../models/schema');
|
||||
const db = require('../../models/db');
|
||||
|
||||
var redis = require('../../helpers/redis');
|
||||
var mailer = require('../../helpers/mailer');
|
||||
var uploader = require('../../helpers/uploader');
|
||||
var space_render = require('../../helpers/space-render');
|
||||
@ -12,13 +11,11 @@ var async = require('async');
|
||||
var moment = require('moment');
|
||||
var fs = require('fs');
|
||||
var _ = require("underscore");
|
||||
var mongoose = require("mongoose");
|
||||
var archiver = require('archiver');
|
||||
var request = require('request');
|
||||
var url = require("url");
|
||||
var path = require("path");
|
||||
var crypto = require('crypto');
|
||||
var qr = require('qr-image');
|
||||
var glob = require('glob');
|
||||
var gm = require('gm');
|
||||
var sanitizeHtml = require('sanitize-html');
|
||||
@ -49,18 +46,10 @@ var roleMapping = {
|
||||
|
||||
router.get('/png', function(req, res, next) {
|
||||
var triggered = new Date();
|
||||
|
||||
var s3_filename = "s" + req.space._id + "/" + "thumb_" + triggered.getTime() + ".jpg";
|
||||
|
||||
if (!req.space.thumbnail_updated_at || req.space.thumbnail_updated_at < req.space.updated_at || !req.space.thumbnail_url) {
|
||||
|
||||
Space.update({
|
||||
"_id": req.space._id
|
||||
}, {
|
||||
"$set": {
|
||||
thumbnail_updated_at: triggered
|
||||
}
|
||||
}, function(a, b, c) {});
|
||||
db.Space.update({ thumbnail_updated_at: triggered }, {where : {"_id": req.space._id }});
|
||||
|
||||
phantom.takeScreenshot(req.space, "png",
|
||||
function(local_path) {
|
||||
@ -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) {
|
||||
|
||||
if (err) {
|
||||
console.error("screenshot resize error: ", err);
|
||||
console.error("[space screenshot] resize error: ", err);
|
||||
res.status(500).send("Error taking screenshot.");
|
||||
return;
|
||||
}
|
||||
@ -76,22 +65,15 @@ router.get('/png', function(req, res, next) {
|
||||
uploader.uploadFile(s3_filename, "image/jpeg", localResizedFilePath, function(err, thumbnailUrl) {
|
||||
|
||||
if (err) {
|
||||
console.error("screenshot s3 upload error. filename: " + s3_filename + " details: ", err);
|
||||
console.error("[space screenshot] upload error. filename: " + s3_filename + " details: ", err);
|
||||
res.status(500).send("Error uploading screenshot.");
|
||||
return;
|
||||
}
|
||||
|
||||
var oldUrl = req.space.thumbnail_url;
|
||||
|
||||
Space.update({
|
||||
"_id": req.space._id
|
||||
}, {
|
||||
"$set": {
|
||||
thumbnail_url: thumbnailUrl
|
||||
}
|
||||
}, function(a, b, c) {
|
||||
db.Space.update({ thumbnail_url: thumbnailUrl }, {where : {"_id": req.space._id }}).then(() => {
|
||||
res.redirect(thumbnailUrl);
|
||||
|
||||
try {
|
||||
if (oldUrl) {
|
||||
var oldPath = url.parse(oldUrl).pathname;
|
||||
@ -125,77 +107,6 @@ function make_export_filename(space, extension) {
|
||||
return space.name.replace(/[^\w]/g, '') + "-" + space._id + "-" + moment().format("YYYYMMDD-HH-mm-ss") + "." + extension;
|
||||
}
|
||||
|
||||
router.get('/list', function(req, res, next) {
|
||||
|
||||
if (req.user) {
|
||||
if (req.spaceRole == "admin" || req.spaceRole == "editor") {
|
||||
|
||||
if (req.space.space_type == "space") {
|
||||
Artifact.find({
|
||||
space_id: req.space._id
|
||||
}).exec(function(err, artifacts) {
|
||||
async.map(artifacts, function(a, cb) {
|
||||
if (a.user_id) {
|
||||
User.findOne({
|
||||
"_id": a.user_id
|
||||
}).exec(function(err, user) {
|
||||
a.user = user;
|
||||
|
||||
if (a.last_update_user_id) {
|
||||
User.findOne({
|
||||
"_id": a.last_update_user_id
|
||||
}).exec(function(err, updateUser) {
|
||||
a.update_user = updateUser;
|
||||
cb(null, a);
|
||||
});
|
||||
} else {
|
||||
cb(null, a);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
cb(null, a);
|
||||
}
|
||||
}, function(err, mappedArtifacts) {
|
||||
|
||||
req.space.artifacts = mappedArtifacts.map(function(a) {
|
||||
a.description = sanitizeHtml(a.description, {
|
||||
allowedTags: [],
|
||||
allowedAttributes: []
|
||||
});
|
||||
|
||||
if (a.payload_uri) {
|
||||
var parsed = url.parse(a.payload_uri);
|
||||
var fileName = path.basename(parsed.pathname) || "file.bin";
|
||||
a.filename = fileName;
|
||||
}
|
||||
|
||||
return a;
|
||||
});
|
||||
|
||||
res.render('artifact_list', {
|
||||
space: req.space
|
||||
});
|
||||
});
|
||||
});
|
||||
} else {
|
||||
Space.getRecursiveSubspacesForSpace(req.space, (err, subspaces) => {
|
||||
res.render('space_list', {
|
||||
subspaces: subspaces.map((s) => {
|
||||
s.ae_link = config.endpoint + '/s/' + s.edit_hash + (s.edit_slug ? ('-'+s.edit_slug) : '')
|
||||
return s;
|
||||
}),
|
||||
space: req.space
|
||||
});
|
||||
});
|
||||
}
|
||||
} else {
|
||||
res.sendStatus(403);
|
||||
}
|
||||
} else {
|
||||
res.sendStatus(403);
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/pdf', function(req, res, next) {
|
||||
var s3_filename = make_export_filename(req.space, "pdf");
|
||||
|
||||
@ -329,36 +240,14 @@ router.get('/zip', function(req, res, next) {
|
||||
});
|
||||
|
||||
router.get('/html', function(req, res) {
|
||||
Artifact.find({
|
||||
console.log("!!!!! hello ");
|
||||
db.Artifact.findAll({where: {
|
||||
space_id: req.space._id
|
||||
}, function(err, artifacts) {
|
||||
}}).then(function(artifacts) {
|
||||
var space = req.space;
|
||||
res.send(space_render.render_space_as_html(space, artifacts));
|
||||
});
|
||||
});
|
||||
|
||||
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;
|
||||
|
||||
|
@ -1,57 +1,31 @@
|
||||
"use strict";
|
||||
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 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 fs = require('fs');
|
||||
var _ = require("underscore");
|
||||
var mongoose = require("mongoose");
|
||||
var archiver = require('archiver');
|
||||
var request = require('request');
|
||||
var url = require("url");
|
||||
var path = require("path");
|
||||
var crypto = require('crypto');
|
||||
var qr = require('qr-image');
|
||||
var glob = require('glob');
|
||||
var gm = require('gm');
|
||||
var crypto = require('crypto');
|
||||
|
||||
var express = require('express');
|
||||
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) {
|
||||
Membership
|
||||
.find({
|
||||
space: req.space._id
|
||||
})
|
||||
.populate("user")
|
||||
.exec(function(err, memberships) {
|
||||
db.Membership
|
||||
.findAll({where: {
|
||||
space_id: req.space._id
|
||||
}, include: ['user']})
|
||||
.then(memberships => {
|
||||
res.status(200).json(memberships);
|
||||
});
|
||||
});
|
||||
@ -59,26 +33,26 @@ router.get('/', function(req, res, next) {
|
||||
router.post('/', function(req, res, next) {
|
||||
if (req.spaceRole == "admin") {
|
||||
var attrs = req.body;
|
||||
attrs['space'] = req.space._id;
|
||||
attrs['state'] = "pending";
|
||||
var membership = new Membership(attrs);
|
||||
attrs.space_id = req.space._id;
|
||||
attrs.state = "pending";
|
||||
attrs._id = uuidv4();
|
||||
var membership = attrs;
|
||||
|
||||
var msg = attrs.personal_message;
|
||||
|
||||
if (membership.email_invited != req.user.email) {
|
||||
User.findOne({
|
||||
db.User.findOne({where:{
|
||||
"email": membership.email_invited
|
||||
}, function(err, user) {
|
||||
}}).then(function(user) {
|
||||
|
||||
if (user) {
|
||||
membership.user = user;
|
||||
membership.user_id = user._id;
|
||||
membership.state = "active";
|
||||
} else {
|
||||
membership.code = crypto.randomBytes(64).toString('hex').substring(0, 12);
|
||||
}
|
||||
|
||||
membership.save(function(err) {
|
||||
if (err) res.sendStatus(400);
|
||||
else {
|
||||
db.Membership.create(membership).then(function() {
|
||||
var accept_link = config.endpoint + "/accept/" + membership._id + "?code=" + membership.code;
|
||||
|
||||
if (user) {
|
||||
@ -104,7 +78,6 @@ router.post('/', function(req, res, next) {
|
||||
});
|
||||
|
||||
res.status(201).json(membership);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
@ -125,22 +98,16 @@ router.post('/', function(req, res, next) {
|
||||
router.put('/:membership_id', function(req, res, next) {
|
||||
if (req.user) {
|
||||
if (req.spaceRole == "admin") {
|
||||
Membership.findOne({
|
||||
db.Membership.findOne({ where: {
|
||||
_id: req.params.membership_id
|
||||
}, function(err, mem) {
|
||||
if (err) res.sendStatus(400);
|
||||
else {
|
||||
}}).then(function(mem) {
|
||||
if (mem) {
|
||||
var attrs = req.body;
|
||||
mem.role = attrs.role;
|
||||
mem.save(function(err) {
|
||||
if (err) res.sendStatus(400);
|
||||
else {
|
||||
mem.save(function() {
|
||||
res.status(201).json(mem);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
res.sendStatus(403);
|
||||
@ -152,20 +119,12 @@ router.put('/:membership_id', function(req, res, next) {
|
||||
|
||||
router.delete('/:membership_id', function(req, res, next) {
|
||||
if (req.user) {
|
||||
Membership.findOne({
|
||||
db.Membership.findOne({ where: {
|
||||
_id: req.params.membership_id
|
||||
}, function(err, mem) {
|
||||
if (err) res.sendStatus(400);
|
||||
else {
|
||||
mem.remove(function(err) {
|
||||
if (err) {
|
||||
res.status(400).json(err);
|
||||
} else {
|
||||
// FIXME might need to delete the user?
|
||||
}}).then(function(mem) {
|
||||
mem.destroy().then(function() {
|
||||
res.sendStatus(204);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
res.sendStatus(403);
|
||||
|
@ -1,6 +1,9 @@
|
||||
"use strict";
|
||||
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 mailer = require('../../helpers/mailer');
|
||||
@ -11,15 +14,12 @@ var phantom = require('../../helpers/phantom');
|
||||
var async = require('async');
|
||||
var fs = require('fs');
|
||||
var _ = require("underscore");
|
||||
var mongoose = require("mongoose");
|
||||
var archiver = require('archiver');
|
||||
var request = require('request');
|
||||
var url = require("url");
|
||||
var path = require("path");
|
||||
var crypto = require('crypto');
|
||||
var qr = require('qr-image');
|
||||
var glob = require('glob');
|
||||
var gm = require('gm');
|
||||
|
||||
var express = require('express');
|
||||
var router = express.Router({mergeParams: true});
|
||||
@ -49,90 +49,44 @@ var roleMapping = {
|
||||
// MESSAGES
|
||||
|
||||
router.get('/', function(req, res, next) {
|
||||
Message.find({
|
||||
space: req.space._id
|
||||
}).populate('user', userMapping).exec(function(err, messages) {
|
||||
db.Message.findAll({where:{
|
||||
space_id: req.space._id
|
||||
}, include: ['user']})
|
||||
.then(function(messages) {
|
||||
res.status(200).json(messages);
|
||||
});
|
||||
});
|
||||
|
||||
router.post('/', function(req, res, next) {
|
||||
var attrs = req.body;
|
||||
attrs.space = req.space;
|
||||
attrs.space_id = req.space._id;
|
||||
|
||||
if (req.user) {
|
||||
attrs.user = req.user;
|
||||
attrs.user_id = req.user._id;
|
||||
} else {
|
||||
attrs.user = null;
|
||||
}
|
||||
|
||||
var msg = new Message(attrs);
|
||||
msg.save(function(err) {
|
||||
if (err) res.status(400).json(erra);
|
||||
else {
|
||||
var msg = attrs;
|
||||
msg._id = uuidv4();
|
||||
|
||||
db.Message.create(msg).then(function() {
|
||||
if (msg.message.length <= 1) return;
|
||||
|
||||
Membership
|
||||
.find({
|
||||
space: req.space,
|
||||
user: {
|
||||
"$exists": true
|
||||
}
|
||||
})
|
||||
.populate('user')
|
||||
.exec(function(err, memberships) {
|
||||
var users = memberships.map(function(m) {
|
||||
return m.user;
|
||||
});
|
||||
users.forEach((user) => {
|
||||
if (user.preferences.email_notifications) {
|
||||
redis.isOnlineInSpace(user, req.space, function(err, online) {
|
||||
if (!online) {
|
||||
var nickname = msg.editor_name;
|
||||
if (req.user) {
|
||||
nickname = req.user.nickname;
|
||||
}
|
||||
mailer.sendMail(
|
||||
user.email,
|
||||
req.i18n.__("space_message_subject", req.space.name),
|
||||
req.i18n.__("space_message_body", nickname, req.space.name), {
|
||||
message: msg.message,
|
||||
action: {
|
||||
link: config.endpoint + "/spaces/" + req.space._id.toString(),
|
||||
name: req.i18n.__("open")
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.log("not sending message to user: is online.");
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.log("not sending message to user: is disabled notifications.");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// TODO reimplement notifications
|
||||
res.distributeCreate("Message", msg);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
router.delete('/:message_id', function(req, res, next) {
|
||||
Message.findOne({
|
||||
db.Message.findOne({where:{
|
||||
"_id": req.params.message_id
|
||||
}, function(err, msg) {
|
||||
}}).then(function(msg) {
|
||||
if (!msg) {
|
||||
res.sendStatus(404);
|
||||
} else {
|
||||
msg.remove(function(err) {
|
||||
if (err) res.status(400).json(err);
|
||||
else {
|
||||
if (msg) {
|
||||
msg.destroy().then(function() {
|
||||
res.distributeDelete("Message", msg);
|
||||
} else {
|
||||
res.sendStatus(404);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -1,6 +1,9 @@
|
||||
"use strict";
|
||||
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 mailer = require('../../helpers/mailer');
|
||||
@ -14,13 +17,10 @@ var slug = require('slug');
|
||||
var fs = require('fs');
|
||||
var async = require('async');
|
||||
var _ = require("underscore");
|
||||
var mongoose = require("mongoose");
|
||||
var archiver = require('archiver');
|
||||
var request = require('request');
|
||||
var url = require("url");
|
||||
var path = require("path");
|
||||
var crypto = require('crypto');
|
||||
var qr = require('qr-image');
|
||||
var glob = require('glob');
|
||||
var gm = require('gm');
|
||||
const exec = require('child_process');
|
||||
@ -48,15 +48,14 @@ router.get('/', function(req, res, next) {
|
||||
});
|
||||
} else {
|
||||
if (req.query.writablefolders) {
|
||||
Membership.find({
|
||||
user: req.user._id
|
||||
}, (err, memberships) => {
|
||||
db.Membership.find({where: {
|
||||
user_id: req.user._id
|
||||
}}, (memberships) => {
|
||||
|
||||
var validMemberships = memberships.filter((m) => {
|
||||
if (!m.space || (m.space == "undefined"))
|
||||
if (!m.space_id || (m.space_id == "undefined"))
|
||||
return false;
|
||||
else
|
||||
return mongoose.Types.ObjectId.isValid(m.space.toString());
|
||||
return true;
|
||||
});
|
||||
|
||||
var editorMemberships = validMemberships.filter((m) => {
|
||||
@ -64,9 +63,10 @@ router.get('/', function(req, res, next) {
|
||||
});
|
||||
|
||||
var spaceIds = editorMemberships.map(function(m) {
|
||||
return new mongoose.Types.ObjectId(m.space);
|
||||
return m.space_id;
|
||||
});
|
||||
|
||||
// TODO port
|
||||
var q = {
|
||||
"space_type": "folder",
|
||||
"$or": [{
|
||||
@ -81,13 +81,11 @@ router.get('/', function(req, res, next) {
|
||||
}]
|
||||
};
|
||||
|
||||
Space
|
||||
.find(q)
|
||||
.populate('creator', userMapping)
|
||||
.exec(function(err, spaces) {
|
||||
if (err) console.error(err);
|
||||
db.Space
|
||||
.findAll({where: q})
|
||||
.then(function(spaces) {
|
||||
var updatedSpaces = spaces.map(function(s) {
|
||||
var spaceObj = s.toObject();
|
||||
var spaceObj = s; //.toObject();
|
||||
return spaceObj;
|
||||
});
|
||||
|
||||
@ -104,62 +102,55 @@ router.get('/', function(req, res, next) {
|
||||
return s.space_type == "folder";
|
||||
})
|
||||
var uniqueFolders = _.unique(onlyFolders, (s) => {
|
||||
return s._id.toString();
|
||||
return s._id;
|
||||
})
|
||||
|
||||
res.status(200).json(uniqueFolders);
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
} else if (req.query.search) {
|
||||
|
||||
Membership.find({
|
||||
user: req.user._id
|
||||
}, function(err, memberships) {
|
||||
db.Membership.findAll({where:{
|
||||
user_id: req.user._id
|
||||
}}).then(memberships => {
|
||||
|
||||
var validMemberships = memberships.filter(function(m) {
|
||||
if (!m.space || (m.space == "undefined"))
|
||||
if (!m.space_id || (m.space_id == "undefined"))
|
||||
return false;
|
||||
else
|
||||
return mongoose.Types.ObjectId.isValid(m.space.toString());
|
||||
return true;
|
||||
});
|
||||
|
||||
var spaceIds = validMemberships.map(function(m) {
|
||||
return new mongoose.Types.ObjectId(m.space);
|
||||
return m.space_id;
|
||||
});
|
||||
|
||||
var q = {
|
||||
"$or": [{"creator": req.user._id},
|
||||
{"_id": {"$in": spaceIds}},
|
||||
{"parent_space_id": {"$in": spaceIds}}],
|
||||
name: new RegExp(req.query.search, "i")
|
||||
};
|
||||
// TODO FIXME port
|
||||
var q = { where: {
|
||||
[Op.or]: [{"creator_id": req.user._id},
|
||||
{"_id": {[Op.in]: spaceIds}},
|
||||
{"parent_space_id": {[Op.in]: spaceIds}}],
|
||||
name: {[Op.like]: "%"+req.query.search+"%"}
|
||||
}, include: ['creator']};
|
||||
|
||||
Space
|
||||
.find(q)
|
||||
.populate('creator', userMapping)
|
||||
.exec(function(err, spaces) {
|
||||
if (err) console.error(err);
|
||||
var updatedSpaces = spaces.map(function(s) {
|
||||
var spaceObj = s.toObject();
|
||||
return spaceObj;
|
||||
});
|
||||
db.Space
|
||||
.findAll(q)
|
||||
.then(function(spaces) {
|
||||
res.status(200).json(spaces);
|
||||
});
|
||||
});
|
||||
|
||||
} else if (req.query.parent_space_id && req.query.parent_space_id != req.user.home_folder_id) {
|
||||
|
||||
Space
|
||||
.findOne({
|
||||
db.Space
|
||||
.findOne({where: {
|
||||
_id: req.query.parent_space_id
|
||||
})
|
||||
.populate('creator', userMapping)
|
||||
.exec(function(err, space) {
|
||||
}})
|
||||
//.populate('creator', userMapping)
|
||||
.then(function(space) {
|
||||
if (space) {
|
||||
Space.roleInSpace(space, req.user, function(err, role) {
|
||||
|
||||
db.getUserRoleInSpace(space, req.user, function(role) {
|
||||
if (role == "none") {
|
||||
if (space.access_mode == "public") {
|
||||
role = "viewer";
|
||||
@ -167,12 +158,11 @@ router.get('/', function(req, res, next) {
|
||||
}
|
||||
|
||||
if (role != "none") {
|
||||
Space
|
||||
.find({
|
||||
db.Space
|
||||
.findAll({where:{
|
||||
parent_space_id: req.query.parent_space_id
|
||||
})
|
||||
.populate('creator', userMapping)
|
||||
.exec(function(err, spaces) {
|
||||
}, include:['creator']})
|
||||
.then(function(spaces) {
|
||||
res.status(200).json(spaces);
|
||||
});
|
||||
} else {
|
||||
@ -185,41 +175,39 @@ router.get('/', function(req, res, next) {
|
||||
});
|
||||
|
||||
} else {
|
||||
Membership.find({
|
||||
user: req.user._id
|
||||
}, function(err, memberships) {
|
||||
db.Membership.findAll({ where: {
|
||||
user_id: req.user._id
|
||||
}}).then(memberships => {
|
||||
if (!memberships) memberships = [];
|
||||
|
||||
var validMemberships = memberships.filter(function(m) {
|
||||
if (!m.space || (m.space == "undefined"))
|
||||
if (!m.space_id || (m.space_id == "undefined"))
|
||||
return false;
|
||||
else
|
||||
return mongoose.Types.ObjectId.isValid(m.space.toString());
|
||||
});
|
||||
|
||||
var spaceIds = validMemberships.map(function(m) {
|
||||
return new mongoose.Types.ObjectId(m.space);
|
||||
return m.space_id;
|
||||
});
|
||||
|
||||
var q = {
|
||||
"$or": [{
|
||||
"creator": req.user._id,
|
||||
[Op.or]: [{
|
||||
"creator_id": req.user._id,
|
||||
"parent_space_id": req.user.home_folder_id
|
||||
}, {
|
||||
"_id": {
|
||||
"$in": spaceIds
|
||||
[Op.in]: spaceIds
|
||||
},
|
||||
"creator": {
|
||||
"$ne": req.user._id
|
||||
"creator_id": {
|
||||
[Op.ne]: req.user._id
|
||||
}
|
||||
}]
|
||||
};
|
||||
|
||||
Space
|
||||
.find(q)
|
||||
.populate('creator', userMapping)
|
||||
.exec(function(err, spaces) {
|
||||
if (err) console.error(err);
|
||||
db.Space
|
||||
.findAll({where: q, include: ['creator']})
|
||||
.then(function(spaces) {
|
||||
var updatedSpaces = spaces.map(function(s) {
|
||||
var spaceObj = s.toObject();
|
||||
var spaceObj = db.spaceToObject(s);
|
||||
return spaceObj;
|
||||
});
|
||||
res.status(200).json(spaces);
|
||||
@ -229,47 +217,43 @@ router.get('/', function(req, res, next) {
|
||||
}
|
||||
});
|
||||
|
||||
// create a space
|
||||
router.post('/', function(req, res, next) {
|
||||
if (req.user) {
|
||||
var attrs = req.body;
|
||||
|
||||
var createSpace = () => {
|
||||
|
||||
attrs.creator = req.user;
|
||||
attrs._id = uuidv4();
|
||||
attrs.creator_id = req.user._id;
|
||||
attrs.edit_hash = crypto.randomBytes(64).toString('hex').substring(0, 7);
|
||||
attrs.edit_slug = slug(attrs.name);
|
||||
|
||||
var space = new Space(attrs);
|
||||
space.save(function(err, createdSpace) {
|
||||
if (err) res.sendStatus(400);
|
||||
else {
|
||||
var membership = new Membership({
|
||||
user: req.user,
|
||||
space: createdSpace,
|
||||
db.Space.create(attrs).then(createdSpace => {
|
||||
//if (err) res.sendStatus(400);
|
||||
var membership = {
|
||||
_id: uuidv4(),
|
||||
user_id: req.user._id,
|
||||
space_id: attrs._id,
|
||||
role: "admin"
|
||||
});
|
||||
membership.save(function(err, createdTeam) {
|
||||
if (err) {
|
||||
res.status(400).json(err);
|
||||
} else {
|
||||
};
|
||||
|
||||
db.Membership.create(membership).then(() => {
|
||||
res.status(201).json(createdSpace);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (attrs.parent_space_id) {
|
||||
Space.findOne({
|
||||
db.Space.findOne({ where: {
|
||||
"_id": attrs.parent_space_id
|
||||
}).populate('creator', userMapping).exec((err, parentSpace) => {
|
||||
}}).then(parentSpace => {
|
||||
if (parentSpace) {
|
||||
Space.roleInSpace(parentSpace, req.user, (err, role) => {
|
||||
db.getUserRoleInSpace(parentSpace, req.user, (role) => {
|
||||
if ((role == "editor") || (role == "admin")) {
|
||||
createSpace();
|
||||
} else {
|
||||
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);
|
||||
});
|
||||
|
||||
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) {
|
||||
var space = req.space;
|
||||
var newAttr = req.body;
|
||||
@ -308,24 +316,17 @@ router.put('/:id', function(req, res) {
|
||||
delete newAttr['editor_name'];
|
||||
delete newAttr['creator'];
|
||||
|
||||
Space.findOneAndUpdate({
|
||||
db.Space.update(newAttr, {where: {
|
||||
"_id": space._id
|
||||
}, {
|
||||
"$set": newAttr
|
||||
}, {
|
||||
"new": true
|
||||
}, function(err, space) {
|
||||
if (err) res.status(400).json(err);
|
||||
else {
|
||||
}}).then(space => {
|
||||
res.distributeUpdate("Space", space);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
router.post('/:id/background', function(req, res, next) {
|
||||
var space = req.space;
|
||||
var newDate = new Date();
|
||||
var fileName = (req.query.filename || "upload.bin").replace(/[^a-zA-Z0-9\.]/g, '');
|
||||
var fileName = (req.query.filename || "upload.jpg").replace(/[^a-zA-Z0-9\.]/g, '');
|
||||
var localFilePath = "/tmp/" + fileName;
|
||||
var writeStream = fs.createWriteStream(localFilePath);
|
||||
var stream = req.pipe(writeStream);
|
||||
@ -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) {
|
||||
if (err) res.status(400).json(err);
|
||||
else {
|
||||
var adv = space.advanced;
|
||||
|
||||
if (adv.background_uri) {
|
||||
var oldPath = url.parse(req.space.thumbnail_url).pathname;
|
||||
if (space.background_uri) {
|
||||
var oldPath = url.parse(req.space.background_uri).pathname;
|
||||
uploader.removeFile(oldPath, function(err) {
|
||||
console.log("removed old bg error:", err);
|
||||
console.error("removed old bg error:", err);
|
||||
});
|
||||
}
|
||||
|
||||
adv.background_uri = backgroundUrl;
|
||||
|
||||
Space.findOneAndUpdate({
|
||||
"_id": space._id
|
||||
db.Space.update({
|
||||
background_uri: backgroundUrl
|
||||
}, {
|
||||
"$set": {
|
||||
advanced: adv
|
||||
}
|
||||
}, {
|
||||
"new": true
|
||||
}, function(err, space) {
|
||||
if (err) {
|
||||
res.sendStatus(400);
|
||||
} else {
|
||||
where: { "_id": space._id }
|
||||
}, function(rows) {
|
||||
fs.unlink(localFilePath, function(err) {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
@ -365,7 +355,6 @@ router.post('/:id/background', function(req, res, next) {
|
||||
res.status(200).json(space);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
@ -390,10 +379,10 @@ router.post('/:id/duplicate', (req, res, next) => {
|
||||
}).populate('creator', userMapping).exec((err, parentSpace) => {
|
||||
if (!parentSpace) {
|
||||
res.status(404).json({
|
||||
"error": "parent space not found for dupicate"
|
||||
"error": "parent space not found for duplicate"
|
||||
});
|
||||
} else {
|
||||
Space.roleInSpace(parentSpace, req.user, (err, role) => {
|
||||
db.getUserRoleInSpace(parentSpace, req.user, (role) => {
|
||||
if (role == "admin" || role == "editor") {
|
||||
handleDuplicateSpaceRequest(req, res, parentSpace);
|
||||
} else {
|
||||
@ -415,15 +404,12 @@ router.delete('/:id', function(req, res, next) {
|
||||
|
||||
if (req.spaceRole == "admin") {
|
||||
const attrs = req.body;
|
||||
Space.recursiveDelete(space, function(err) {
|
||||
if (err) res.status(400).json(err);
|
||||
else {
|
||||
space.destroy().then(function() {
|
||||
res.distributeDelete("Space", space);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
res.status(403).json({
|
||||
"error": "requires admin status"
|
||||
"error": "requires admin role"
|
||||
});
|
||||
}
|
||||
} else {
|
||||
@ -449,6 +435,7 @@ router.post('/:id/artifacts-pdf', function(req, res, next) {
|
||||
fs.mkdir(outputFolder, function(db) {
|
||||
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) {
|
||||
if (error === null) {
|
||||
|
||||
@ -532,6 +519,7 @@ router.post('/:id/artifacts-pdf', function(req, res, next) {
|
||||
|
||||
}, function(err, artifacts) {
|
||||
|
||||
// FIXME not portable
|
||||
exec.execFile("rm", ["-r", outputFolder], function(err) {
|
||||
res.status(201).json(_.flatten(artifacts));
|
||||
|
||||
@ -551,6 +539,7 @@ router.post('/:id/artifacts-pdf', function(req, res, next) {
|
||||
});
|
||||
} else {
|
||||
console.error("error:", error);
|
||||
// FIXME not portable
|
||||
exec.execFile("rm", ["-r", outputFolder], function(err) {
|
||||
fs.unlink(localFilePath);
|
||||
res.status(400).json({});
|
||||
|
@ -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;
|
@ -1,14 +1,15 @@
|
||||
"use strict";
|
||||
|
||||
var config = require('config');
|
||||
require('../../models/schema');
|
||||
const db = require('../../models/db');
|
||||
const uuidv4 = require('uuid/v4');
|
||||
|
||||
var mailer = require('../../helpers/mailer');
|
||||
var uploader = require('../../helpers/uploader');
|
||||
var importer = require('../../helpers/importer');
|
||||
|
||||
var bcrypt = require('bcryptjs');
|
||||
var crypo = require('crypto');
|
||||
var crypto = require('crypto');
|
||||
var swig = require('swig');
|
||||
var async = require('async');
|
||||
var _ = require('underscore');
|
||||
@ -20,6 +21,7 @@ var URL = require('url').URL;
|
||||
|
||||
var express = require('express');
|
||||
var router = express.Router();
|
||||
var glob = require('glob');
|
||||
|
||||
router.get('/current', function(req, res, next) {
|
||||
if (req.user) {
|
||||
@ -30,49 +32,64 @@ router.get('/current', function(req, res, next) {
|
||||
}
|
||||
});
|
||||
|
||||
// create user
|
||||
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 nickname = req.body["nickname"];
|
||||
var password = req.body["password"];
|
||||
var password_confirmation = req.body["password_confirmation"];
|
||||
|
||||
if (password_confirmation == password) {
|
||||
if (validator.isEmail(email)) {
|
||||
if (password_confirmation != password) {
|
||||
res.status(400).json({"error":"password_confirmation"});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!validator.isEmail(email)) {
|
||||
res.status(400).json({"error":"email_invalid"});
|
||||
return;
|
||||
}
|
||||
|
||||
var createUser = function() {
|
||||
bcrypt.genSalt(10, function(err, salt) {
|
||||
bcrypt.hash(password, salt, function(err, hash) {
|
||||
|
||||
crypo.randomBytes(16, function(ex, buf) {
|
||||
crypto.randomBytes(16, function(ex, buf) {
|
||||
var token = buf.toString('hex');
|
||||
|
||||
var u = new User({
|
||||
var u = {
|
||||
_id: uuidv4(),
|
||||
email: email,
|
||||
account_type: "email",
|
||||
nickname: nickname,
|
||||
password_hash: hash,
|
||||
preferences: {
|
||||
language: req.i18n.locale
|
||||
},
|
||||
prefs_language: req.i18n.locale,
|
||||
confirmation_token: token
|
||||
});
|
||||
};
|
||||
|
||||
u.save(function (err) {
|
||||
if (err) res.sendStatus(400);
|
||||
else {
|
||||
var homeSpace = new Space({
|
||||
db.User.create(u)
|
||||
.error(err => {
|
||||
res.sendStatus(400);
|
||||
})
|
||||
.then(u => {
|
||||
var homeSpace = {
|
||||
_id: uuidv4(),
|
||||
name: req.i18n.__("home"),
|
||||
space_type: "folder",
|
||||
creator: u
|
||||
});
|
||||
|
||||
homeSpace.save((err, homeSpace) => {
|
||||
if (err) res.sendStatus(400);
|
||||
else {
|
||||
creator_id: u._id
|
||||
};
|
||||
db.Space.create(homeSpace)
|
||||
.error(err => {
|
||||
res.sendStatus(400);
|
||||
})
|
||||
.then(homeSpace => {
|
||||
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"), {
|
||||
action: {
|
||||
@ -80,181 +97,30 @@ router.post('/', function(req, res) {
|
||||
name: req.i18n.__("confirm_action")
|
||||
}
|
||||
});
|
||||
|
||||
if (err) res.status(400).json(err);
|
||||
else {
|
||||
res.status(201).json({});
|
||||
}
|
||||
})
|
||||
.error(err => {
|
||||
res.status(400).json(err);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
User.find({email: email}, (function (err, users) {
|
||||
if (err) {
|
||||
res.status(400).json({"error":"password_confirmation"});
|
||||
} else {
|
||||
|
||||
if (users.length === 0) {
|
||||
var domain = email.slice(email.lastIndexOf('@')+1);
|
||||
|
||||
Domain.findOne({domain: domain}, function(err, domain) {
|
||||
if(domain){
|
||||
if(domain.edu) {
|
||||
db.User.findAll({where: {email: email}})
|
||||
.then(users => {
|
||||
if (users.length == 0) {
|
||||
//var domain = email.slice(email.lastIndexOf('@')+1);
|
||||
createUser();
|
||||
} else {
|
||||
res.status(400).json({"error":"domain_blocked"});
|
||||
}
|
||||
} else {
|
||||
createUser();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
res.status(400).json({"error":"user_email_already_used"});
|
||||
}
|
||||
}
|
||||
}));
|
||||
} else {
|
||||
res.status(400).json({"error":"email_invalid"});
|
||||
}
|
||||
} else {
|
||||
res.status(400).json({"error":"password_confirmation"});
|
||||
}
|
||||
} else {
|
||||
res.status(400).json({"error":"email or password missing"});
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
router.get('/oauth2callback/url', function(req, res) {
|
||||
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) {
|
||||
router.get('/current', function(req, res, next) {
|
||||
if (req.user) {
|
||||
console.log(req.user.team);
|
||||
res.status(200).json(req.user);
|
||||
} else {
|
||||
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) {
|
||||
// TODO explicit whitelisting
|
||||
var user = req.user;
|
||||
console.log(req.params.id, user._id);
|
||||
if (user._id == req.params.id) {
|
||||
var newAttr = req.body;
|
||||
newAttr.updated_at = new Date();
|
||||
delete newAttr['_id'];
|
||||
|
||||
User.findOneAndUpdate({"_id": user._id}, {"$set": newAttr}, function(err, updatedUser) {
|
||||
if (err) {
|
||||
res.sendStatus(400);
|
||||
} else {
|
||||
res.status(200).json(updatedUser);
|
||||
}
|
||||
db.User.update(newAttr, {where: {"_id": user._id}}).then(function(updatedUser) {
|
||||
res.status(200).json(newAttr);
|
||||
});
|
||||
} else {
|
||||
res.sendStatus(403);
|
||||
@ -292,12 +154,8 @@ router.post('/:id/password', function(req, res, next) {
|
||||
bcrypt.genSalt(10, function(err, salt) {
|
||||
bcrypt.hash(pass, salt, function(err, hash) {
|
||||
user.password_hash = hash;
|
||||
user.save(function(err){
|
||||
if(err){
|
||||
res.status(400).json(err);
|
||||
}else{
|
||||
user.save().then(function() {
|
||||
res.sendStatus(204);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -370,19 +228,15 @@ router.post('/:user_id/avatar', (req, res, next) => {
|
||||
if (err) res.status(400).json(err);
|
||||
else {
|
||||
user.avatar_thumb_uri = url;
|
||||
user.save((err, updatedUser) => {
|
||||
if (err) {
|
||||
res.sendStatus(400);
|
||||
} else {
|
||||
user.save().then(() => {
|
||||
fs.unlink(localResizedFilePath, (err) => {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
res.status(400).json(err);
|
||||
} 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) => {
|
||||
const email = req.query.email;
|
||||
User.findOne({"email": email}).exec((err, user) => {
|
||||
if (err) {
|
||||
res.status(400).json(err);
|
||||
} else {
|
||||
db.User.findOne({where: {"email": email}}).then((user) => {
|
||||
if (user) {
|
||||
if(user.account_type == "email") {
|
||||
crypo.randomBytes(16, (ex, buf) => {
|
||||
crypto.randomBytes(16, (ex, buf) => {
|
||||
user.password_reset_token = buf.toString('hex');
|
||||
user.save((err, updatedUser) => {
|
||||
if (err) res.status(400).json(err);
|
||||
else {
|
||||
user.save().then(updatedUser => {
|
||||
mailer.sendMail(email, req.i18n.__("password_reset_subject"), req.i18n.__("password_reset_body"), {action: {
|
||||
link: config.endpoint + "/password-confirm/" + user.password_reset_token,
|
||||
name: req.i18n.__("password_reset_action")
|
||||
}});
|
||||
res.status(201).json({});
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
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;
|
||||
|
||||
User
|
||||
.findOne({"password_reset_token": req.params.confirm_token})
|
||||
.exec((err, user) => {
|
||||
if (err) {
|
||||
res.sendStatus(400);
|
||||
} else {
|
||||
.findOne({where: {"password_reset_token": req.params.confirm_token}})
|
||||
.then((user) => {
|
||||
if (user) {
|
||||
bcrypt.genSalt(10, (err, salt) => {
|
||||
bcrypt.hash(password, salt, function(err, hash) {
|
||||
@ -456,7 +296,6 @@ router.post('/password_reset_requests/:confirm_token/confirm', function(req, res
|
||||
} else {
|
||||
res.sendStatus(404);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@ -468,6 +307,12 @@ router.post('/:user_id/confirm', function(req, res, next) {
|
||||
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) {
|
||||
if (req.query.zip) {
|
||||
res.send("importing");
|
||||
|
@ -1,7 +1,7 @@
|
||||
"use strict";
|
||||
|
||||
var config = require('config');
|
||||
require('../../models/schema');
|
||||
require('../../models/db');
|
||||
|
||||
var fs = require('fs');
|
||||
var phantom = require('node-phantom-simple');
|
||||
|
108
routes/root.js
108
routes/root.js
@ -1,7 +1,7 @@
|
||||
"use strict";
|
||||
|
||||
const config = require('config');
|
||||
require('../models/schema');
|
||||
require('../models/db');
|
||||
|
||||
const redis = require('../helpers/redis');
|
||||
const express = require('express');
|
||||
@ -9,7 +9,6 @@ const crypto = require('crypto');
|
||||
const router = express.Router();
|
||||
const mailer = require('../helpers/mailer');
|
||||
const _ = require('underscore');
|
||||
const qr = require('qr-image');
|
||||
|
||||
router.get('/', (req, res) => {
|
||||
res.render('index', { title: 'Spaces' });
|
||||
@ -95,10 +94,6 @@ router.get('/logout', (req, res) => {
|
||||
res.render('spacedeck');
|
||||
});
|
||||
|
||||
router.get('/users/oauth2callback', (req, res) => {
|
||||
res.render('spacedeck');
|
||||
});
|
||||
|
||||
router.get('/contact', (req, res) => {
|
||||
res.render('public/contact');
|
||||
});
|
||||
@ -185,107 +180,6 @@ router.get('/spaces/:id', (req, res) => {
|
||||
} 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) {
|
||||
Space.findOne({"_id": req.params.id}).exec(function(err, space) {
|
||||
if (space) {
|
||||
|
169
spacedeck.js
Normal file
169
spacedeck.js
Normal 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);
|
||||
}
|
||||
});*/
|
@ -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>
|
||||
|
||||
<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-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>
|
||||
</p>
|
||||
</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 class="collapse" v-bind:class="{in:account=='language'}">
|
||||
<div class="modal-section">
|
||||
<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>
|
||||
</label>
|
||||
<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>
|
||||
</label>
|
||||
<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>
|
||||
</label>
|
||||
</div>
|
||||
@ -104,8 +112,8 @@
|
||||
<div class="modal-section labels-inline">
|
||||
<div class="form-group">
|
||||
<label class="checkbox"
|
||||
v-bind:class="{checked: user.preferences.email_notifications}"
|
||||
v-on:click="account_save_user_notifications(!user.preferences.email_notifications);">
|
||||
v-bind:class="{checked: user.prefs_email_notifications}"
|
||||
v-on:click="account_save_user_notifications(!user.prefs_email_notifications);">
|
||||
<span>[[__('notifications_option_chat')]]</span>
|
||||
</label>
|
||||
</div>
|
||||
|
@ -1,9 +1,9 @@
|
||||
<div id="space" class="section board active mouse-{{mouse_state}} tool-{{active_tool}}">
|
||||
|
||||
<div class="space-bounds" style="width:{{active_space.advanced.width*bounds_zoom}}px;height:{{active_space.advanced.height*bounds_zoom}}px;"></div>
|
||||
<div class="space-bounds" style="width:{{active_space.width*bounds_zoom}}px;height:{{active_space.height*bounds_zoom}}px;"></div>
|
||||
|
||||
<div class="wrapper"
|
||||
style="transform:scale({{viewport_zoom}});transform-origin:0 0;width:{{active_space.advanced.width}}px;height:{{active_space.advanced.height}}px;background-image:url('{{active_space.advanced.background_uri}}');background-color:{{active_space.advanced.background_color}};margin-left:{{bounds_margin_horiz}}px;margin-top:{{bounds_margin_vert}}px" >
|
||||
style="transform:scale({{viewport_zoom}});transform-origin:0 0;width:{{active_space.width}}px;height:{{active_space.height}}px;background-image:url('{{active_space.background_uri}}');background-color:{{active_space.background_color}};margin-left:{{bounds_margin_horiz}}px;margin-top:{{bounds_margin_vert}}px" >
|
||||
|
||||
<div v-repeat="a : active_space_artifacts"
|
||||
v-class="text-editing:(editing_artifact_id==a._id && (a.view.major_type=='text' || a.view.major_type=='shape'))"
|
||||
@ -81,7 +81,7 @@
|
||||
</div>
|
||||
|
||||
<div class="tl-controls">
|
||||
<div class="btn btn-md btn-toggle btn-round" v-class="alt:a.player_view.state=='playing'" v-show="a.board.w>=200 || a.player_view.state!='playing'">
|
||||
<div class="btn btn-md btn-toggle btn-round" v-class="alt:a.player_view.state=='playing'" v-show="a.w>=200 || a.player_view.state!='playing'">
|
||||
<span class="btn-option play">
|
||||
<span class="icon icon-controls-play"></span>
|
||||
</span>
|
||||
@ -95,8 +95,8 @@
|
||||
<span class="icon icon-controls-stop"></span>
|
||||
</span>
|
||||
|
||||
<span class="tl-title" v-show="a.board.w>=250 && a.board.h>=150">{{a.view.filename}}</span>
|
||||
<span class="tl-times" class="btn-group" v-show="a.board.w>=400 && a.board.h>=150">
|
||||
<span class="tl-title" v-show="a.w>=250 && a.h>=150">{{a.view.filename}}</span>
|
||||
<span class="tl-times" class="btn-group" v-show="a.w>=400 && a.h>=150">
|
||||
<span class="btn btn-md btn-transparent no-p set-inpoint">{{a.player_view.current_time_string}} /</span>
|
||||
<span class="btn btn-md btn-transparent no-p set-outpoint">{{a.player_view.total_time_string}}</span>
|
||||
</span>
|
||||
|
@ -198,7 +198,7 @@
|
||||
<div id="space" v-cloak
|
||||
v-if="active_view == 'space' && active_space_loaded"
|
||||
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-whiteboard
|
||||
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 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"
|
||||
v-bind:style="{
|
||||
transform: 'scale('+viewport_zoom+')',
|
||||
'transform-origin': '0 0',
|
||||
width: active_space.advanced.width + 'px',
|
||||
height: active_space.advanced.height + 'px',
|
||||
'background-image': (active_space.advanced.background_uri)?'url(' + active_space.advanced.background_uri + ')':'',
|
||||
'background-color': ''+active_space.advanced.background_color,
|
||||
width: active_space.width + 'px',
|
||||
height: active_space.height + 'px',
|
||||
'background-image': (active_space.background_uri)?'url(' + active_space.background_uri + ')':'',
|
||||
'background-color': ''+active_space.background_color,
|
||||
'margin-left': bounds_margin_horiz + '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"/>
|
||||
</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-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>
|
||||
@ -352,13 +352,13 @@
|
||||
<span class="icon icon-controls-stop"></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="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 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">
|
||||
<span class="icon icon-edge-left"></span>
|
||||
</a>
|
||||
@ -464,7 +464,7 @@
|
||||
|
||||
<div v-if="active_space_loaded" v-cloak>
|
||||
<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-on:mousedown="handle_minimap_mousedown($event)"
|
||||
v-on:touchstart="handle_minimap_mousedown($event)"
|
||||
@ -473,7 +473,7 @@
|
||||
v-on:mouseleave="handle_minimap_mouseup($event)"
|
||||
v-on:touchend="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>
|
||||
|
@ -79,14 +79,14 @@
|
||||
</div-->
|
||||
|
||||
<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 class="progress state-processing" v-if="space_background_uploading">
|
||||
<div class="spinner"></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">
|
||||
<span class="icon icon-picture-upload"></span>
|
||||
<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>
|
||||
</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">
|
||||
<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)">
|
||||
<span class="icon icon-picture-upload"></span>
|
||||
<!-- Upload -->
|
||||
|
@ -1,7 +1,7 @@
|
||||
<div id="pick-mobile" v-if="active_space" class="dialog-section" v-show="opened_dialog=='mobile'">
|
||||
<h4 class="dialog-title">Mobile Upload</h4>
|
||||
|
||||
<img v-if="active_space.edit_hash" v-bind:src="'/api/helper/qrcode/'+ active_space._id" />
|
||||
<!--img v-if="active_space.edit_hash" v-bind:src="'/api/helper/qrcode/'+ active_space._id"-->
|
||||
|
||||
<p class="text-center">
|
||||
Install the Spacedeck App on your phone and scan this QR code to upload photos, sound, video or text.
|
||||
|
@ -77,19 +77,6 @@
|
||||
</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>
|
||||
|
||||
<div class="dropdown bottom light center" v-show="logged_in" v-bind:class="{open:opened_dialog=='background'}">
|
||||
|
@ -9,7 +9,7 @@
|
||||
<button v-on:click="add_zone()" class="btn btn-sm btn-primary">[[__("add_zone")]]</button>
|
||||
</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 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>
|
||||
|
@ -13,6 +13,8 @@
|
||||
<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 ]]">
|
||||
|
||||
<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>
|
||||
@ -83,6 +85,8 @@
|
||||
<script minify src="/javascripts/spacedeck_directives.js"></script>
|
||||
<script minify src="/javascripts/spacedeck_vue.js"></script>
|
||||
{% endif %}
|
||||
|
||||
<script>if (window.module) module = window.module;</script>
|
||||
</head>
|
||||
|
||||
<body id="main" v-bind:class="{'present-mode':present_mode,'modal-open':active_modal}" v-on:click="handle_body_click($event)">
|
||||
|
Loading…
Reference in New Issue
Block a user