diff --git a/README.md b/README.md index 33963c9..594365c 100644 --- a/README.md +++ b/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 diff --git a/app.js b/app.js index 004fb5a..73d7fd5 100644 --- a/app.js +++ b/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 - })); -} - -// 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 -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'); +// 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() } +}) -}).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; +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() } - - 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); - } -}); +}) diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index 688f381..0000000 --- a/docker-compose.yml +++ /dev/null @@ -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 diff --git a/helpers/artifact_converter.js b/helpers/artifact_converter.js index d6f468d..76c22e1 100644 --- a/helpers/artifact_converter.js +++ b/helpers/artifact_converter.js @@ -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,46 +259,41 @@ 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 { - fs.unlink(originalFilePath, function (err) { - if (err){ - console.error(err); - payloadCallback(err, null); - } else { - console.log('successfully deleted ' + originalFilePath); - payloadCallback(null, a); - } - }); - } + a.save().then(function() { + fs.unlink(originalFilePath, function (err) { + if (err){ + console.error(err); + payloadCallback(err, null); + } else { + console.log('successfully deleted ' + originalFilePath); + 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,36 +324,31 @@ 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 { - fs.unlink(localFilePath, function (err) { - if (err){ - console.error(err); - payloadCallback(err, null); - } else { - console.log('successfully deleted ' + localFilePath); - payloadCallback(null, a); - } - }); - } + a.save().then(function() { + fs.unlink(localFilePath, function (err) { + if (err){ + console.error(err); + payloadCallback(err, null); + } else { + console.log('successfully deleted ' + localFilePath); + 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,43 +514,40 @@ module.exports = { ]; a.updated_at = new Date(); - a.save(function(err){ - if(err) payloadCallback(err, null); - else { - fs.unlink(localFilePath, function (err) { - if (err){ - console.error(err); - payloadCallback(err, null); - } else { - console.log('successfully deleted ' + localFilePath); - payloadCallback(null, a); - } - }); - } + + db.packArtifact(a); + + a.save().then(function(){ + fs.unlink(localFilePath, function (err) { + if (err){ + console.error(err); + payloadCallback(err, null); + } else { + console.log('successfully deleted ' + localFilePath); + 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 { - fs.unlink(localFilePath, function (err) { - payloadCallback(null, a); - }); - } + a.save().then(function() { + fs.unlink(localFilePath, function (err) { + payloadCallback(null, a); + }); }); }); } diff --git a/helpers/importer.js b/helpers/importer.js index 90eb2a9..4d50946 100644 --- a/helpers/importer.js +++ b/helpers/importer.js @@ -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,17 +69,35 @@ 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 { @@ -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."); }); - } + }*/ } }; diff --git a/helpers/phantom.js b/helpers/phantom.js index ed897f5..197b9a2 100644 --- a/helpers/phantom.js +++ b/helpers/phantom.js @@ -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(); } @@ -32,16 +33,16 @@ module.exports = { }; phantom.create({ path: require('phantomjs-prebuilt').path }, function (err, browser) { - if(err){ - console.log(err); - }else{ + if (err) { + console.error(err); + } else { return browser.createPage(function (err, page) { console.log("page created, opening ",space_url); if (type=="pdf") { var psz = { - width: space.advanced.width+"px", - height: space.advanced.height+"px" + width: space.width+"px", + height: space.height+"px" }; page.set('paperSize', psz); } diff --git a/helpers/websockets.js b/helpers/websockets.js index d541ede..394e45c 100644 --- a/helpers/websockets.js +++ b/helpers/websockets.js @@ -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 { @@ -268,10 +270,10 @@ module.exports = { }, distributeUsers: function(spaceId) { - if(!spaceId) + 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)); + }.bind(this));*/ } }; diff --git a/middlewares/404.js b/middlewares/404.js index ac38434..2854608 100644 --- a/middlewares/404.js +++ b/middlewares/404.js @@ -1,6 +1,6 @@ 'use strict'; -require('../models/schema'); +require('../models/db'); var config = require('config'); module.exports = (req, res, next) => { @@ -16,4 +16,4 @@ module.exports = (req, res, next) => { } else { res.status(404).send("Not Found."); } -} \ No newline at end of file +} diff --git a/middlewares/api_helpers.js b/middlewares/api_helpers.js index e6c844a..893f35f 100644 --- a/middlewares/api_helpers.js +++ b/middlewares/api_helpers.js @@ -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); }; diff --git a/middlewares/artifact_helpers.js b/middlewares/artifact_helpers.js index 4df5d58..b436150 100644 --- a/middlewares/artifact_helpers.js +++ b/middlewares/artifact_helpers.js @@ -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); + }}).then(artifact => { + if (artifact) { + req['artifact'] = artifact; + next(); } else { - if (artifact) { - req['artifact'] = artifact; - next(); - } else { - res.sendStatus(404); - } + res.sendStatus(404); } }); -}; \ No newline at end of file +}; diff --git a/middlewares/cors.js b/middlewares/cors.js index 09c4875..d5b0900 100644 --- a/middlewares/cors.js +++ b/middlewares/cors.js @@ -1,6 +1,6 @@ 'use strict'; -require('../models/schema'); +require('../models/db'); const config = require('config'); const url = require('url'); @@ -26,20 +26,20 @@ module.exports = (req, res, next) => { const parsedUrl = url.parse(origin, true, true); // FIXME - if (parsedUrl.hostname == "cdn.spacedeck.com") { + if (parsedUrl.hostname == "cdn.spacedeck.com") { res.header('Cache-Control', "max-age"); res.header('Expires', "30d"); res.removeHeader("Pragma"); 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 { diff --git a/middlewares/i18n.js b/middlewares/i18n.js index a28203b..32ce411 100644 --- a/middlewares/i18n.js +++ b/middlewares/i18n.js @@ -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(); -} \ No newline at end of file +} diff --git a/middlewares/session.js b/middlewares/session.js new file mode 100644 index 0000000..3060637 --- /dev/null +++ b/middlewares/session.js @@ -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(); + } +} + diff --git a/middlewares/setuser.js b/middlewares/setuser.js deleted file mode 100644 index 56bcd78..0000000 --- a/middlewares/setuser.js +++ /dev/null @@ -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(); - } -} diff --git a/middlewares/space_helpers.js b/middlewares/space_helpers.js index 96a9357..07e5931 100644 --- a/middlewares/space_helpers.js +++ b/middlewares/space_helpers.js @@ -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,64 +53,66 @@ 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 { - if (space) { + }}).then(function(space) { - if (space.access_mode == "public") { + //.populate("creator", userMapping) + //if (err) { + // res.status(400).json(err); + //} else { - if (space.password) { - if (req.spacePassword) { - if (req.spacePassword === space.password) { - finalizeAnonymousLogin(space, req["spaceAuth"]); - } else { - res.status(403).json({ - "error": "password_wrong" - }); - } - } else { - res.status(401).json({ - "error": "password_required" - }); - } - } else { - finalizeAnonymousLogin(space, req["spaceAuth"]); - } - - } else { - // 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"); - return; - } - - if (req.user) { - rolePerUser(space, req.user, function(role) { - if (role == "none") { - finalizeAnonymousLogin(space, req["spaceAuth"]); - } else { - finalizeReq(space, role); - } - }); - } else { - if (req.spaceAuth && space.edit_hash) { + if (space) { + if (space.access_mode == "public") { + if (space.password) { + if (req.spacePassword) { + if (req.spacePassword === space.password) { finalizeAnonymousLogin(space, req["spaceAuth"]); } else { res.status(403).json({ - "error": "auth_required" + "error": "password_wrong" }); } + } else { + res.status(401).json({ + "error": "password_required" + }); + } + } else { + finalizeAnonymousLogin(space, req["spaceAuth"]); + } + + } 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"); + return; + } + + if (req.user) { + db.getUserRoleInSpace(space, req.user, function(role) { + if (role == "none") { + finalizeAnonymousLogin(space, req["spaceAuth"]); + } else { + finalizeReq(space, role); + } + }); + } else { + if (req.spaceAuth && space.edit_hash) { + finalizeAnonymousLogin(space, req["spaceAuth"]); + } else { + res.status(403).json({ + "error": "auth_required" + }); } } - } else { - res.status(404).json({ - "error": "space_not_found" - }); } + } else { + res.status(404).json({ + "error": "space_not_found" + }); } }); } diff --git a/middlewares/subdomain.js b/middlewares/subdomain.js deleted file mode 100644 index 2ce6a6a..0000000 --- a/middlewares/subdomain.js +++ /dev/null @@ -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(); - } - }); -} diff --git a/middlewares/team_helpers.js b/middlewares/team_helpers.js deleted file mode 100644 index b072043..0000000 --- a/middlewares/team_helpers.js +++ /dev/null @@ -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" - }); - } -} \ No newline at end of file diff --git a/middlewares/templates.js b/middlewares/templates.js deleted file mode 100644 index 4aebb63..0000000 --- a/middlewares/templates.js +++ /dev/null @@ -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(); -} \ No newline at end of file diff --git a/models/action.js b/models/action.js index 71a0da4..61cf38b 100644 --- a/models/action.js +++ b/models/action.js @@ -1,5 +1,7 @@ 'use strict'; +// FIXME port this last model + var mongoose = require('mongoose'); var Schema = mongoose.Schema; diff --git a/models/artifact.js b/models/artifact.js deleted file mode 100644 index 61d7e4a..0000000 --- a/models/artifact.js +++ /dev/null @@ -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} -}); diff --git a/models/db.js b/models/db.js new file mode 100644 index 0000000..70e8f19 --- /dev/null +++ b/models/db.js @@ -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; + } +} diff --git a/models/domain.js b/models/domain.js deleted file mode 100644 index b2be7db..0000000 --- a/models/domain.js +++ /dev/null @@ -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 -}); diff --git a/models/membership.js b/models/membership.js deleted file mode 100644 index 44b9b70..0000000 --- a/models/membership.js +++ /dev/null @@ -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 -}); - diff --git a/models/message.js b/models/message.js deleted file mode 100644 index 5cbfe0e..0000000 --- a/models/message.js +++ /dev/null @@ -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 -}); diff --git a/models/plan.js b/models/plan.js deleted file mode 100644 index b0c3815..0000000 --- a/models/plan.js +++ /dev/null @@ -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; \ No newline at end of file diff --git a/models/schema.js b/models/schema.js deleted file mode 100644 index e64911b..0000000 --- a/models/schema.js +++ /dev/null @@ -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); diff --git a/models/space.js b/models/space.js deleted file mode 100644 index e8ff3f6..0000000 --- a/models/space.js +++ /dev/null @@ -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; diff --git a/models/team.js b/models/team.js deleted file mode 100644 index b35942c..0000000 --- a/models/team.js +++ /dev/null @@ -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); - } -} diff --git a/models/user.js b/models/user.js deleted file mode 100644 index e1c6887..0000000 --- a/models/user.js +++ /dev/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); -}; diff --git a/package.json b/package.json index fcfa957..f2af01e 100644 --- a/package.json +++ b/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", diff --git a/public/javascripts/backend.js b/public/javascripts/backend.js index 9b7bb08..5739fea 100644 --- a/public/javascripts/backend.js +++ b/public/javascripts/backend.js @@ -133,7 +133,15 @@ function load_spaces(id, is_home, on_success, on_error) { }, on_error); } -function load_writable_folders( on_success, 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); } diff --git a/public/javascripts/spacedeck_account.js b/public/javascripts/spacedeck_account.js index fd64917..06f6846 100644 --- a/public/javascripts/spacedeck_account.js +++ b/public/javascripts/spacedeck_account.js @@ -8,24 +8,29 @@ 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.save_user(function(){ + this.user.prefs_email_digest = val; + this.save_user(function() { }); }, account_save_user_notifications: function(val) { - this.user.preferences.email_notifications = val; - this.save_user(function(){ + 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.save_user(function() { - window._spacedeck_location_change = true; - location.href="/spaces"; - }.bind(this)); - } + this.user.prefs_language = lang; + this.save_user(function() { + window._spacedeck_location_change = true; + location.href="/spaces"; + }.bind(this)); }, save_user: function(on_success) { diff --git a/public/javascripts/spacedeck_board_artifacts.js b/public/javascripts/spacedeck_board_artifacts.js index 5ec3aba..2915f99 100644 --- a/public/javascripts/spacedeck_board_artifacts.js +++ b/public/javascripts/spacedeck_board_artifacts.js @@ -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,11 +102,9 @@ 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); if (this.artifact_is_text_blank(a)) { @@ -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,26 +190,22 @@ var SpacedeckBoardArtifacts = { var styles = []; var z = 0; - if (a.board) { - z = a.board.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", - "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"); - } + z = a.z; + if (z<0) z=0; // fix negative z-index + + styles = [ + "left:" +a.x+"px", + "top:" +a.y+"px", + "width:" +a.w+"px", + "height:"+a.h+"px", + "z-index:"+z + ]; + 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")) { styles.push("overflow:visible"); @@ -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 ""; } 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 ""; } 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 ""; } else return "Can't resolve: "+a.payload_uri; } else if (provider=="soundcloud") { - return ''; + return ''; } 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 = ""; + var svg = ""; svg += path_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; i999) 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" - } + 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 = "

Text

"; } @@ -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; ieff_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); diff --git a/public/javascripts/spacedeck_spaces.js b/public/javascripts/spacedeck_spaces.js index 55526ce..b528b83 100644 --- a/public/javascripts/spacedeck_spaces.js +++ b/public/javascripts/spacedeck_spaces.js @@ -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(); @@ -636,17 +636,14 @@ var SpacedeckSpaces = { download_space: function() { smoke.quiz(__("download_space"), function(e, test) { - if (e == "PDF"){ + if (e == "PDF") { this.download_space_as_pdf(this.active_space); - }else if (e == "ZIP"){ + } else if (e == "ZIP") { this.download_space_as_zip(this.active_space); - }else if (e == "TXT"){ - this.download_space_as_list(this.active_space); } }.bind(this), { button_1: "PDF", button_2: "ZIP", - button_3: "TXT", button_cancel:__("cancel") }); diff --git a/public/javascripts/spacedeck_users.js b/public/javascripts/spacedeck_users.js index 59a66d2..4cbd60d 100644 --- a/public/javascripts/spacedeck_users.js +++ b/public/javascripts/spacedeck_users.js @@ -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(); diff --git a/public/javascripts/spacedeck_vue.js b/public/javascripts/spacedeck_vue.js index ead97d7..141cb1a 100644 --- a/public/javascripts/spacedeck_vue.js +++ b/public/javascripts/spacedeck_vue.js @@ -158,7 +158,7 @@ function boot_spacedeck() { }); } -$(document).ready(function(){ +document.addEventListener("DOMContentLoaded",function() { window.smoke = smoke; window.alert = smoke.alert; diff --git a/public/javascripts/spacedeck_whiteboard.js b/public/javascripts/spacedeck_whiteboard.js index c6ffd2e..9bdb4ec 100644 --- a/public/javascripts/spacedeck_whiteboard.js +++ b/public/javascripts/spacedeck_whiteboard.js @@ -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 (xd2x2) { 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 - }) + 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 }; } diff --git a/public/javascripts/vector-render.js b/public/javascripts/vector-render.js index 84adb91..4d4bc9f 100644 --- a/public/javascripts/vector-render.js +++ b/public/javascripts/vector-render.js @@ -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 = ""; - tip += ""; - var svg = tip + ""; + tip += ""; + var svg = tip + ""; 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 ""; diff --git a/routes/api/memberships.js b/routes/api/memberships.js index f01c0e7..f25cd1d 100644 --- a/routes/api/memberships.js +++ b/routes/api/memberships.js @@ -1,56 +1,40 @@ "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 { - if (mem) { - if(!mem.user) { - mem.code = null; - mem.state = "active"; - mem.user = req.user; - - mem.save(function(err){ - if (err) res.status(400).json(err); - else { - console.log(mem); - res.status(200).json(mem); - } - }); - } else { - res.status(400).json({"error": "already_used"}); - } + code: req.query.code + }, include: ['space']}).then((mem) => { + if (mem) { + if (!mem.user) { + mem.state = "active"; + mem.user_id = req.user._id; + + mem.save().then(function() { + res.status(200).json(mem); + }); } else { - res.status(404).json({"error": "not found"}); + res.status(200).json(mem); } + } else { + res.status(404).json({"error": "not found"}); } }); } else { diff --git a/routes/api/sessions.js b/routes/api/sessions.js index 054e49c..8c2fdba 100644 --- a/routes/api/sessions.js +++ b/routes/api/sessions.js @@ -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,68 +12,64 @@ var router = express.Router(); router.post('/', function(req, res) { var data = req.body; - if (data.email && data.password) { - 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]; - - if (bcrypt.compareSync(password, user.password_hash)) { - crypo.randomBytes(48, function(ex, buf) { - var token = buf.toString('hex'); - - var session = { - token: token, - ip: req.ip, - device: "web", - created_at: new Date() - }; - - if (!user.sessions) - user.sessions = []; - - user.sessions.push(session); - - user.save(function(err, result) { - if (err) console.error("Error saving user:",err); - - var domain = (process.env.NODE_ENV == "production") ? new URL(config.get('endpoint')).hostname : "localhost"; - - res.cookie('sdsession', token, { domain: domain, httpOnly: true }); - res.status(201).json(session); - }); - }); - }else{ - res.sendStatus(403); - } - } else { - res.sendStatus(404); - } - } - })); - } else { + if (!data.email || !data.password) { res.status(400).json({}); + return; } + + var email = req.body.email.toLowerCase(); + var password = req.body["password"]; + + db.User.findOne({where: {email: email}}) + .error(err => { + res.sendStatus(404); + //res.status(400).json({"error":"session.users"}); + }) + .then(user => { + console.log("!!! user: ",user.password_hash); + + if (bcrypt.compareSync(password, user.password_hash)) { + crypto.randomBytes(48, function(ex, buf) { + var token = buf.toString('hex'); + console.log("!!! token: ",token); + + var session = { + user_id: user._id, + token: token, + ip: req.ip, + device: "web", + created_at: new Date() + }; + + 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); + }); + }); + } else { + res.sendStatus(403); + } + }); }); 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); } diff --git a/routes/api/space_artifacts.js b/routes/api/space_artifacts.js index d3e5006..c11b8ce 100644 --- a/routes/api/space_artifacts.js +++ b/routes/api/space_artifacts.js @@ -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() - } - }); - res.distributeCreate("Artifact", artifact); - } + 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); @@ -161,42 +163,23 @@ router.put('/:artifact_id', function(req, res, next) { } else { newAttr.last_update_editor_name = req.editor_name; } + + db.packArtifact(newAttr); - Artifact.findOneAndUpdate({ + 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() - } - }); - res.distributeDelete("Artifact", artifact); - } + db.Artifact.destroy({where: { "_id": artifact._id}}).then(() => { + db.Space.update({ updated_at: new Date() }, {where: {_id: req.space._id} }); + res.distributeDelete("Artifact", artifact); }); }); diff --git a/routes/api/space_digest.js b/routes/api/space_digest.js index cee3557..cbdf490 100644 --- a/routes/api/space_digest.js +++ b/routes/api/space_digest.js @@ -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, diff --git a/routes/api/space_exports.js b/routes/api/space_exports.js index 3a28f71..2b7b15b 100644 --- a/routes/api/space_exports.js +++ b/routes/api/space_exports.js @@ -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,26 +46,18 @@ var roleMapping = { router.get('/png', function(req, res, next) { var triggered = new Date(); - var s3_filename = "s" + req.space._id + "/" + "thumb_" + triggered.getTime() + ".jpg"; if (!req.space.thumbnail_updated_at || req.space.thumbnail_updated_at < req.space.updated_at || !req.space.thumbnail_url) { - - Space.update({ - "_id": req.space._id - }, { - "$set": { - thumbnail_updated_at: triggered - } - }, function(a, b, c) {}); - + db.Space.update({ thumbnail_updated_at: triggered }, {where : {"_id": req.space._id }}); + phantom.takeScreenshot(req.space, "png", function(local_path) { var localResizedFilePath = local_path + ".thumb.jpg"; gm(local_path).resize(640, 480).quality(70.0).autoOrient().write(localResizedFilePath, function(err) { if (err) { - console.error("screenshot resize error: ", err); + console.error("[space screenshot] resize error: ", err); res.status(500).send("Error taking screenshot."); return; } @@ -76,22 +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; + diff --git a/routes/api/space_memberships.js b/routes/api/space_memberships.js index 7ec7b73..2b88dfa 100644 --- a/routes/api/space_memberships.js +++ b/routes/api/space_memberships.js @@ -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,52 +33,51 @@ 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 { - var accept_link = config.endpoint + "/accept/" + membership._id + "?code=" + membership.code; + db.Membership.create(membership).then(function() { + var accept_link = config.endpoint + "/accept/" + membership._id + "?code=" + membership.code; - if (user) { - accept_link = config.endpoint + "/" + req.space.space_type + "s/" + req.space._id; - } - - var openText = req.i18n.__("space_invite_membership_action"); - if (user) { - req.i18n.__("open"); - } - - const name = req.user.nickname || req.user.email - const subject = (req.space.space_type == "space") ? req.i18n.__("space_invite_membership_subject", name, req.space.name) : req.i18n.__("folder_invite_membership_subject", req.user.nickname, req.space.name) - const body = (req.space.space_type == "space") ? req.i18n.__("space_invite_membership_body", name, req.space.name) : req.i18n.__("folder_invite_membership_body", req.user.nickname, req.space.name) - - mailer.sendMail( - membership.email_invited, subject, body, { - messsage: msg, - action: { - link: accept_link, - name: openText - } - }); - - res.status(201).json(membership); + if (user) { + accept_link = config.endpoint + "/" + req.space.space_type + "s/" + req.space._id; } + + var openText = req.i18n.__("space_invite_membership_action"); + if (user) { + req.i18n.__("open"); + } + + const name = req.user.nickname || req.user.email + const subject = (req.space.space_type == "space") ? req.i18n.__("space_invite_membership_subject", name, req.space.name) : req.i18n.__("folder_invite_membership_subject", req.user.nickname, req.space.name) + const body = (req.space.space_type == "space") ? req.i18n.__("space_invite_membership_body", name, req.space.name) : req.i18n.__("folder_invite_membership_body", req.user.nickname, req.space.name) + + mailer.sendMail( + membership.email_invited, subject, body, { + messsage: msg, + action: { + link: accept_link, + name: openText + } + }); + + res.status(201).json(membership); }); }); @@ -125,21 +98,15 @@ 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 { - if (mem) { - var attrs = req.body; - mem.role = attrs.role; - mem.save(function(err) { - if (err) res.sendStatus(400); - else { - res.status(201).json(mem); - } - }); - } + }}).then(function(mem) { + if (mem) { + var attrs = req.body; + mem.role = attrs.role; + mem.save(function() { + res.status(201).json(mem); + }); } }); } else { @@ -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? - res.sendStatus(204); - } - }); - } + }}).then(function(mem) { + mem.destroy().then(function() { + res.sendStatus(204); + }); }); } else { res.sendStatus(403); diff --git a/routes/api/space_messages.js b/routes/api/space_messages.js index 47e4a58..400b78d 100644 --- a/routes/api/space_messages.js +++ b/routes/api/space_messages.js @@ -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) { - res.status(200).json(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 { - if (msg.message.length <= 1) return; + var msg = attrs; + msg._id = uuidv4(); - Membership - .find({ - space: req.space, - user: { - "$exists": true - } - }) - .populate('user') - .exec(function(err, memberships) { - var users = memberships.map(function(m) { - return m.user; - }); - users.forEach((user) => { - if (user.preferences.email_notifications) { - redis.isOnlineInSpace(user, req.space, function(err, online) { - if (!online) { - var nickname = msg.editor_name; - if (req.user) { - nickname = req.user.nickname; - } - mailer.sendMail( - user.email, - req.i18n.__("space_message_subject", req.space.name), - req.i18n.__("space_message_body", nickname, req.space.name), { - message: msg.message, - action: { - link: config.endpoint + "/spaces/" + req.space._id.toString(), - name: req.i18n.__("open") - } - }); - } else { - console.log("not sending message to user: is online."); - } - }); - } else { - console.log("not sending message to user: is disabled notifications."); - } - }); - }); - - res.distributeCreate("Message", msg); - } + db.Message.create(msg).then(function() { + if (msg.message.length <= 1) return; + // TODO reimplement notifications + res.distributeCreate("Message", msg); }); }); router.delete('/:message_id', function(req, res, next) { - Message.findOne({ + db.Message.findOne({where:{ "_id": req.params.message_id - }, function(err, msg) { + }}).then(function(msg) { if (!msg) { res.sendStatus(404); } else { - msg.remove(function(err) { - if (err) res.status(400).json(err); - else { - if (msg) { - res.distributeDelete("Message", msg); - } else { - res.sendStatus(404); - } - } + msg.destroy().then(function() { + res.distributeDelete("Message", msg); }); } }); diff --git a/routes/api/spaces.js b/routes/api/spaces.js index ff0545d..57f5704 100644 --- a/routes/api/spaces.js +++ b/routes/api/spaces.js @@ -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,75 +102,67 @@ 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") { + if (space.access_mode == "public") { role = "viewer"; } } 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, - role: "admin" - }); - membership.save(function(err, createdTeam) { - if (err) { - res.status(400).json(err); - } else { - res.status(201).json(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" + }; + + 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 { - res.distributeUpdate("Space", space); - } + }}).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,38 +335,26 @@ router.post('/:id/background', function(req, res, next) { uploader.uploadFile("s" + req.space._id + "/bg_" + newDate.getTime() + "_" + fileName, "image/jpeg", localFilePath, function(err, backgroundUrl) { 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 { - fs.unlink(localFilePath, function(err) { - if (err) { - console.error(err); - res.status(400).json(err); - } else { - res.status(200).json(space); - } - }); - } + where: { "_id": space._id } + }, function(rows) { + fs.unlink(localFilePath, function(err) { + if (err) { + console.error(err); + res.status(400).json(err); + } else { + res.status(200).json(space); + } + }); }); } }); @@ -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 { - res.distributeDelete("Space", space); - } + 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({}); diff --git a/routes/api/teams.js b/routes/api/teams.js deleted file mode 100644 index da46a59..0000000 --- a/routes/api/teams.js +++ /dev/null @@ -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; diff --git a/routes/api/users.js b/routes/api/users.js index 7e1a1aa..f389bf9 100644 --- a/routes/api/users.js +++ b/routes/api/users.js @@ -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,231 +32,95 @@ router.get('/current', function(req, res, next) { } }); +// create user router.post('/', function(req, res) { - if (req.body["email"] && req.body["password"]) { - - var email = req.body["email"].toLowerCase(); - var nickname = req.body["nickname"]; - var password = req.body["password"]; - var password_confirmation = req.body["password_confirmation"]; - - if (password_confirmation == password) { - if (validator.isEmail(email)) { - - var createUser = function() { - bcrypt.genSalt(10, function(err, salt) { - bcrypt.hash(password, salt, function(err, hash) { - - crypo.randomBytes(16, function(ex, buf) { - var token = buf.toString('hex'); - - var u = new User({ - email: email, - account_type: "email", - nickname: nickname, - password_hash: hash, - preferences: { - language: req.i18n.locale - }, - confirmation_token: token - }); - - u.save(function (err) { - if (err) res.sendStatus(400); - else { - var homeSpace = new Space({ - name: req.i18n.__("home"), - space_type: "folder", - creator: u - }); - - homeSpace.save((err, homeSpace) => { - if (err) res.sendStatus(400); - else { - u.home_folder_id = homeSpace._id; - u.save((err) => { - - mailer.sendMail(u.email, req.i18n.__("confirm_subject"), req.i18n.__("confirm_body"), { - action: { - link: config.endpoint + "/confirm/" + u.confirmation_token, - name: req.i18n.__("confirm_action") - } - }); - - if (err) res.status(400).json(err); - else { - res.status(201).json({}); - } - }); - } - }); - } - }); - }); - }); - }); - }; - - User.find({email: email}, (function (err, users) { - if (err) { - res.status(400).json({"error":"password_confirmation"}); - } else { - - if (users.length === 0) { - var domain = email.slice(email.lastIndexOf('@')+1); - - Domain.findOne({domain: domain}, function(err, domain) { - if(domain){ - if(domain.edu) { - createUser(); - } else { - res.status(400).json({"error":"domain_blocked"}); - } - } else { - createUser(); - } - }); - } else { - res.status(400).json({"error":"user_email_already_used"}); - } - } - })); - } else { - res.status(400).json({"error":"email_invalid"}); - } - } else { - res.status(400).json({"error":"password_confirmation"}); - } - } else { + 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"]; -router.get('/oauth2callback/url', function(req, res) { - var google = require('googleapis'); - var OAuth2 = google.auth.OAuth2; + 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) { + crypto.randomBytes(16, function(ex, buf) { + var token = buf.toString('hex'); - var oauth2Client = new OAuth2( - config.google_access, - config.google_secret, - config.endpoint + "/login" - ); + var u = { + _id: uuidv4(), + email: email, + account_type: "email", + nickname: nickname, + password_hash: hash, + prefs_language: req.i18n.locale, + confirmation_token: token + }; - 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); + db.User.create(u) + .error(err => { + res.sendStatus(400); + }) + .then(u => { + var homeSpace = { + _id: uuidv4(), + name: req.i18n.__("home"), + space_type: "folder", + creator_id: u._id + }; + db.Space.create(homeSpace) + .error(err => { + res.sendStatus(400); + }) + .then(homeSpace => { + u.home_folder_id = homeSpace._id; + u.save() + .then(() => { + res.status(201).json({}); + + mailer.sendMail(u.email, req.i18n.__("confirm_subject"), req.i18n.__("confirm_body"), { + action: { + link: config.endpoint + "/confirm/" + u.confirmation_token, + name: req.i18n.__("confirm_action") + } + }); + }) + .error(err => { + res.status(400).json(err); + }); + }) + }); + }); }); }); }; - - 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); - }); - } - }); - } - }); - } - }); - } - }); - } - }); - } - }); + + db.User.findAll({where: {email: email}}) + .then(users => { + if (users.length == 0) { + //var domain = email.slice(email.lastIndexOf('@')+1); + createUser(); + } else { + res.status(400).json({"error":"user_email_already_used"}); + } + }) }); -router.get('/ ', function(req, res, next) { +router.get('/current', function(req, res, next) { if (req.user) { - 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{ - res.sendStatus(204); - } + user.save().then(function() { + res.sendStatus(204); }); }); }); @@ -326,7 +184,7 @@ router.delete('/:id', (req, res, next) => { } } else { user.remove((err) => { - if(err)res.status(400).json(err); + if (err) res.status(400).json(err); else 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 { - fs.unlink(localResizedFilePath, (err) => { - if (err) { - console.error(err); - res.status(400).json(err); - } else { - res.status(200).json(updatedUser); - } - }); - } + user.save().then(() => { + fs.unlink(localResizedFilePath, (err) => { + if (err) { + console.error(err); + res.status(400).json(err); + } else { + res.status(200).json(user); + } + }); }); } }); @@ -400,31 +254,20 @@ 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); + db.User.findOne({where: {"email": email}}).then((user) => { + if (user) { + crypto.randomBytes(16, (ex, buf) => { + user.password_reset_token = buf.toString('hex'); + user.save().then(updatedUser => { + mailer.sendMail(email, req.i18n.__("password_reset_subject"), req.i18n.__("password_reset_body"), {action: { + link: config.endpoint + "/password-confirm/" + user.password_reset_token, + name: req.i18n.__("password_reset_action") + }}); + res.status(201).json({}); + }); + }); } else { - if (user) { - if(user.account_type == "email") { - crypo.randomBytes(16, (ex, buf) => { - user.password_reset_token = buf.toString('hex'); - user.save((err, updatedUser) => { - if (err) res.status(400).json(err); - else { - mailer.sendMail(email, req.i18n.__("password_reset_subject"), req.i18n.__("password_reset_body"), {action: { - link: config.endpoint + "/password-confirm/" + user.password_reset_token, - name: req.i18n.__("password_reset_action") - }}); - res.status(201).json({}); - } - }); - }); - } else { - res.status(404).json({"error": "error_unknown_email"}); - } - } else { - res.status(404).json({"error": "error_unknown_email"}); - } + res.status(404).json({"error": "error_unknown_email"}); } }); }); @@ -433,29 +276,25 @@ 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 { - if(user) { - bcrypt.genSalt(10, (err, salt) => { - bcrypt.hash(password, salt, function(err, hash) { + .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) { - user.password_hash = hash; - user.password_token = null; - user.save(function(err, updatedUser){ - if (err) { - res.sendStatus(400); - } else { - res.sendStatus(201); - } - }); + user.password_hash = hash; + user.password_token = null; + user.save(function(err, updatedUser){ + if (err) { + res.sendStatus(400); + } else { + res.sendStatus(201); + } }); }); - } else { - res.sendStatus(404); - } + }); + } 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"); diff --git a/routes/api/webgrabber.js b/routes/api/webgrabber.js index 519d9aa..17ab0f8 100644 --- a/routes/api/webgrabber.js +++ b/routes/api/webgrabber.js @@ -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'); diff --git a/routes/root.js b/routes/root.js index 1169b69..7819b1b 100644 --- a/routes/root.js +++ b/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) { diff --git a/spacedeck.js b/spacedeck.js new file mode 100644 index 0000000..6b841d1 --- /dev/null +++ b/spacedeck.js @@ -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); + } +});*/ diff --git a/views/partials/account.html b/views/partials/account.html index ff70cfc..7aeaf81 100644 --- a/views/partials/account.html +++ b/views/partials/account.html @@ -1,6 +1,6 @@
- +
[[__("profile_caption")]]
@@ -80,21 +80,29 @@ [[__("confirmation_sent_another")]]

+ +
+ +

No .ZIP files found in Spacedeck application folder.

+
    +
  • {{f}}
  • +
+
@@ -104,8 +112,8 @@