diff --git a/README.md b/README.md
index 33963c960224b90097641c5a3cdd16530bd76456..594365cb828ae0e4b993939053a5890c0c8edb27 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 004fb5a5bd82f1c73708de91b73b4433fd22dcea..73d7fd576777f7944529a3045f31eacc1cf2ecd8 100644
--- a/app.js
+++ b/app.js
@@ -1,180 +1,33 @@
-"use strict";
-
-require('./models/schema');
-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 mongoose = require('mongoose');
-
-const swig = require('swig');
-const i18n = require('i18n-2');
-const helmet = require('helmet');
-
-const express = require('express');
-const app = express();
-const serveStatic = require('serve-static');
-
-const isProduction = app.get('env') === 'production';
-
-console.log("Booting Spacedeck Open… (environment: " + app.get('env') + ")");
-
-app.use(logger(isProduction ? 'combined' : 'dev'));
-
-i18n.expressBind(app, {
-  locales: ["en", "de", "fr"],
-  defaultLocale: "en",
-  cookieName: "spacedeck_locale",
-  devMode: (app.get('env') == 'development')
-});
-
-swig.setDefaults({
-  varControls: ["[[", "]]"] // otherwise it's not compatible with vue.js
-});
-
-swig.setFilter('cdn', function(input, idx) {
-  return input;
-});
-
-app.engine('html', swig.renderFile);
-app.set('view engine', 'html');
-
-if (isProduction) {
-  app.set('views', path.join(__dirname, 'build', 'views'));
-  app.use(favicon(path.join(__dirname, 'build', 'assets', 'images', 'favicon.png')));
-  app.use(express.static(path.join(__dirname, 'build', 'assets')));
-} else {
-  app.set('views', path.join(__dirname, 'views'));
-  app.use(favicon(path.join(__dirname, 'public', 'images', 'favicon.png')));
-  app.use(express.static(path.join(__dirname, 'public')));
-}
-
-app.use(bodyParser.json({
-  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())
-
-// 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'));
+const spacedeck = require('./spacedeck')
+
+const electron = require('electron')
+const electronApp = electron.app
+const BrowserWindow = electron.BrowserWindow
+let mainWindow
+
+function createWindow () {
+  mainWindow = new BrowserWindow({width: 1200, height: 700})
+  mainWindow.loadURL("http://localhost:9666")
+  mainWindow.on('closed', function () {
+    mainWindow = null
+  })
 }
 
-module.exports = app;
+electronApp.on('ready', createWindow)
 
-// 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;
-  }
-
-  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);
+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()
   }
-});
+})
diff --git a/docker-compose.yml b/docker-compose.yml
deleted file mode 100644
index 688f381a1c81bef19525054d5dc488f874f58bf4..0000000000000000000000000000000000000000
--- 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 d6f468d18eb5b64cf9bf7c6ab01825ad05e1c80f..76c22e184ecf4965305b603e496b8b012c52100e 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 90eb2a919cf0b367726d7afff1f4ade19acd0bff..4d50946842a787413354e879b82301394d7691b3 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<artifacts.length; j++) {
             let a = artifacts[j]
             
             let q = {_id: a._id}
-            a.creator = user._id
+            a.user_id = user._id
             delete a.__v
             delete a.payload_thumbnail_big_uri
+            
+            // move nested attrs
+            for (k in a.style) {
+              a[k] = a.style[k]
+            }
+            for (k in a.meta) {
+              a[k] = a.meta[k]
+            }
+            for (k in a.board) {
+              a[k] = a.board[k]
+            }
 
             let prefix = "/storage/"+relativeImportDir+"/"+space._id+"_files/"
             if (a.thumbnail_uri && a.thumbnail_uri[0]!='/') a.thumbnail_uri = prefix + a.thumbnail_uri
@@ -92,8 +115,10 @@ module.exports = {
               }
             }
 
-            Artifact.findOneAndUpdate(q, a, {upsert: true}, function(err,res) {
-              if (err) console.log("[import] artifact upsert err:",err)
+            db.packArtifact(a)
+
+            db.Artifact.create(a).error(function(err) {
+              console.error("[import] artifact upsert err:",err)
             })
           }
         }
diff --git a/helpers/mailer.js b/helpers/mailer.js
index 15545d0d73d6fabe64247af1ca8b41d212ffad48..835dfb900cb58b8a2f9639d6c7574df1d525e4e1 100644
--- a/helpers/mailer.js
+++ b/helpers/mailer.js
@@ -1,7 +1,7 @@
 'use strict';
 
 var swig = require('swig');
-var AWS = require('aws-sdk');
+//var AWS = require('aws-sdk');
 
 module.exports = {
   sendMail: (to_email, subject, body, options) => {
@@ -29,9 +29,9 @@ module.exports = {
       options: options
     });
 
-    if (process.env.NODE_ENV === 'development') {
+    //if (process.env.NODE_ENV === 'development') {
       console.log("Email: to " + to_email + " in production.\nreply_to: " + reply_to + "\nsubject: " + subject + "\nbody: \n" + htmlText + "\n\n plaintext:\n" + plaintext);
-    } else {
+    /*} else {
       AWS.config.update({region: 'eu-west-1'});
       var ses = new AWS.SES();
 
@@ -56,6 +56,6 @@ module.exports = {
         if (err) console.error("Error sending email:", err);
         else console.log("Email sent.");
       });
-    }
+    }*/
   }
 };
diff --git a/helpers/phantom.js b/helpers/phantom.js
index ed897f550205491e1055cce419f4d9e30eb4cccb..197b9a20dd51c6987edb5a00d810b566b52605f2 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 d541ede8cf5e0efde4126880715dc90db96b43cb..394e45c2e52866fa6143a3063f699b058556ceef 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 ac3843447558c7a6e29e0a30236bff295e5f5566..285460849feed6d8bcb83e43b52c9bc2e05f81df 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 e6c844a8813129ea2bb2950901da0b5e80ae0e48..893f35f9e5da602c19c52d1f78ef973fa172b35d 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 4df5d5870fcd89676b85d5c5b8ad258c67c2bbeb..b436150ede737b8b08aa508023d8dad68d0d753c 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 09c48755a85a387c669baf2c5127fa4158cceb81..d5b09008a5472d7bab390b3131ad9810de102b9c 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 a28203bc122f37f6d9f281eb4b73a19b3da26f48..32ce411cee35c69d3423cf31b16e84d989032790 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 0000000000000000000000000000000000000000..30606379f4244f39d70fd0e14a156b39ae5ed57f
--- /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 56bcd787a98d221ce80d1298c7f836785ff428fc..0000000000000000000000000000000000000000
--- 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 96a93579c663ae1c00153c9f63243e8e5b93ef28..07e5931d7893cafe550c8c17ad8d443bb63ae7a3 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"
-                });
-              }
+    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(401).json({
-                "error": "password_required"
+              res.status(403).json({
+                "error": "password_wrong"
               });
             }
           } else {
-            finalizeAnonymousLogin(space, req["spaceAuth"]);
+            res.status(401).json({
+              "error": "password_required"
+            });
           }
-
         } 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;
-          }
+          finalizeAnonymousLogin(space, req["spaceAuth"]);
+        }
 
-          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) {
+      } 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 {
-              res.status(403).json({
-                "error": "auth_required"
-              });
+              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 2ce6a6ad40390db989e5be000999e2b2cde8bbc8..0000000000000000000000000000000000000000
--- 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 b07204356a324aa028a9874c624d6ce351f8b19f..0000000000000000000000000000000000000000
--- 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 4aebb634d11838a67914fc5ca58664e1553ea2f7..0000000000000000000000000000000000000000
--- 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 71a0da4a22bf9c37c62064c57ffe8090d3784cff..61cf38b6bc03a522a6367c6b75a6a628b23328c0 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 61d7e4a0dcd1bcdd5d10cc629c9ae8629cd7cf75..0000000000000000000000000000000000000000
--- 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 0000000000000000000000000000000000000000..70e8f19a3657bc746ad1cd0c6162c7c488ef2481
--- /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 b2be7dbb3ff6249be86706bc75b3ad7eca335b8e..0000000000000000000000000000000000000000
--- 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 44b9b700d956fddb5fa104467134e3071dc2724f..0000000000000000000000000000000000000000
--- 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 5cbfe0e59cac5be768ec8d9f9cd371573dd320fa..0000000000000000000000000000000000000000
--- 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 b0c38150972bc819e54d45be265a48c6dbc483f2..0000000000000000000000000000000000000000
--- 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 e64911ba0c6faef06837b9ff36d4d4ceeec96ba0..0000000000000000000000000000000000000000
--- 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 e8ff3f65be8fd9790c904651fd23a8b9664976a9..0000000000000000000000000000000000000000
--- 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 b35942c8451e2e9813bdfccdd43d8a4b4b810759..0000000000000000000000000000000000000000
--- 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 e1c6887daf9e63be55680acf8a93f04ac7e91ade..0000000000000000000000000000000000000000
--- 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 fcfa957317119d40dc1609383c48b7a840169e25..f2af01e1ba8b1c51921465b9db494c0cce76c304 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 9b7bb08e943cacdd1d225d0d8562dc5c2e09b4b6..5739feac35ce0ec6c0f38ef79bcc7de347273808 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 fd6491702a37845541cdd74a79be3811f8abb02f..06f6846008d316040c9703a31ffbecb42b9f0326 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 5ec3aba34e854685e5942e044d5def39bfa8ed43..2915f99c363b0a29224c904c9ca17e0deb2318b3 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 "<iframe frameborder=0 allowfullscreen src=\""+uri+"?showinfo=0&rel=0&controls=0\"></iframe>";
       } else return "Can't resolve: "+a.payload_uri;
 
     } else if (provider=="dailymotion") {
-      var match = a.meta.link_uri.match(/dailymotion.com\/video\/([^<]*)/);
+      var match = a.link_uri.match(/dailymotion.com\/video\/([^<]*)/);
       if (match && match.length>1) {
         var uri = "https://www.dailymotion.com/embed/video/"+match[1];
         return "<iframe frameborder=0 allowfullscreen src=\""+uri+"\"></iframe>";
       } else return "Can't resolve: "+a.payload_uri;
 
     } else if (provider=="vimeo") {
-      var match = a.meta.link_uri.match(/https?:\/\/(www\.)?vimeo.com\/(\d+)($|\/)/);
+      var match = a.link_uri.match(/https?:\/\/(www\.)?vimeo.com\/(\d+)($|\/)/);
       if (match) {
         var uri = "https://player.vimeo.com/video/"+match[2];
         return "<iframe frameborder=0 allowfullscreen src=\""+uri+"\"></iframe>";
       } else return "Can't resolve: "+a.payload_uri;
 
     } else if (provider=="soundcloud") {
-      return '<iframe width="100%" height="166" scrolling="no" frameborder="no" src="https://w.soundcloud.com/player/?url='+a.meta.link_uri.replace(":", "%3A")+'"></iframe>';
+      return '<iframe width="100%" height="166" scrolling="no" frameborder="no" src="https://w.soundcloud.com/player/?url='+a.link_uri.replace(":", "%3A")+'"></iframe>';
 
     } else if (provider=="spacedeck") {
 
@@ -299,8 +291,8 @@ var SpacedeckBoardArtifacts = {
 
     if (mtype != "vector" && mtype != "shape") return "";
 
-    var shape = a.style.shape || "";
-    var padding = 32 + a.style.stroke*2;
+    var shape = a.shape || "";
+    var padding = 32 + a.stroke*2;
     var path_svg;
     var fill = "";
 
@@ -310,13 +302,13 @@ var SpacedeckBoardArtifacts = {
       fill = "fill:none";
     } else {
       path_svg = render_vector_shape(a, padding);
-      fill = "fill:"+a.style.fill_color+";";
+      fill = "fill:"+a.fill_color+";";
       padding = 0;
     }
     var margin = padding;
 
-    var svg = "<svg xmlns='http://www.w3.org/2000/svg' width='"+(a.board.w+2*padding)+"' height='"+(a.board.h+2*padding)+"' ";
-    svg += "style='margin-left:"+(-margin)+"px;margin-top:"+(-margin)+"px;stroke-width:"+a.style.stroke+";stroke:"+a.style.stroke_color+";"+fill+"'>";
+    var svg = "<svg xmlns='http://www.w3.org/2000/svg' width='"+(a.w+2*padding)+"' height='"+(a.h+2*padding)+"' ";
+    svg += "style='margin-left:"+(-margin)+"px;margin-top:"+(-margin)+"px;stroke-width:"+a.stroke+";stroke:"+a.stroke_color+";"+fill+"'>";
     svg += path_svg;
     svg += "</svg>";
 
@@ -329,10 +321,10 @@ var SpacedeckBoardArtifacts = {
     if (arts.length==0) return null;
 
     r = {
-      x1: parseInt(_.min(arts.map(function(a){return a.board.x}))),
-      y1: parseInt(_.min(arts.map(function(a){return a.board.y}))),
-      x2: parseInt(_.max(arts.map(function(a){return a.board.x+a.board.w}))),
-      y2: parseInt(_.max(arts.map(function(a){return a.board.y+a.board.h})))
+      x1: parseInt(_.min(arts.map(function(a){return a.x}))),
+      y1: parseInt(_.min(arts.map(function(a){return a.y}))),
+      x2: parseInt(_.max(arts.map(function(a){return a.x+a.w}))),
+      y2: parseInt(_.max(arts.map(function(a){return a.y+a.h})))
     };
     r.x=r.x1;
     r.y=r.y1;
@@ -356,7 +348,7 @@ var SpacedeckBoardArtifacts = {
 
   artifacts_in_rect: function(rect) {
     return _.filter(this.active_space_artifacts, function(a) {
-      return this.rects_intersecting(a.board, rect);
+      return this.rects_intersecting(a, rect);
     }.bind(this));
   },
 
@@ -366,15 +358,15 @@ var SpacedeckBoardArtifacts = {
     var rect = this.artifact_selection_rect();
     var overlapping = _.filter(this.artifacts_in_rect(rect), function(a){return !this.is_selected(a)}.bind(this));
 
-    var max_z = _.max(overlapping,function(a){ return a.board.z; });
+    var max_z = _.max(overlapping,function(a){ return a.z; });
     if (max_z.board) {
-      max_z = max_z.board.z + 1;
+      max_z = max_z.z + 1;
     } else {
       max_z = 1;
     }
     
     this.update_selected_artifacts(function(a) {
-      return { board: _.extend(a.board, { z: max_z }) };
+      return { z: max_z };
     });
   },
 
@@ -384,15 +376,15 @@ var SpacedeckBoardArtifacts = {
     var rect = this.artifact_selection_rect();
     var overlapping = _.filter(this.artifacts_in_rect(rect), function(a){return !this.is_selected(a);}.bind(this));
 
-    var min_z = _.min(overlapping,function(a){ return (a.board?a.board.z:0); });
+    var min_z = _.min(overlapping,function(a){ return a.z; });
     if (min_z.board) {
-      min_z = min_z.board.z - 1;
+      min_z = min_z.z - 1;
     } else {
       min_z = 0;
     }
-    var my_z = _.max(this.selected_artifacts(),function(a){ (a.board?a.board.z:0); });
+    var my_z = _.max(this.selected_artifacts(),function(a){ return a.z; });
     if (my_z.board) {
-      my_z = my_z.board.z - 1;
+      my_z = my_z.z - 1;
     } else {
       my_z = 0;
     }
@@ -400,14 +392,14 @@ var SpacedeckBoardArtifacts = {
     // TODO: move all other items up in this case?
     if (min_z < 0) {
       this.update_artifacts(overlapping, function(a) {
-        return { board: _.extend(a.board, { z: (my_z + (a.board?a.board.z:0) + 1) }) };
+        return { z: (my_z + a.z + 1) };
       });
 
       return;
     }
 
     this.update_selected_artifacts(function(a) {
-      return { board: _.extend(a.board, { z: min_z }) };
+      return { z: min_z };
     });
   },
 
@@ -416,7 +408,7 @@ var SpacedeckBoardArtifacts = {
 
     var rect = this.artifact_selection_rect();
     this.update_selected_artifacts(function(a) {
-      return { board: _.extend(a.board, { x: rect.x1 }) };
+      return { x: rect.x1 };
     });
   },
 
@@ -425,7 +417,7 @@ var SpacedeckBoardArtifacts = {
 
     var rect = this.artifact_selection_rect();
     this.update_selected_artifacts(function(a) {
-      return { board: _.extend(a.board, { y: rect.y1 }) };
+      return { y: rect.y1 };
     });
   },
 
@@ -434,7 +426,7 @@ var SpacedeckBoardArtifacts = {
 
     var rect = this.artifact_selection_rect();
     this.update_selected_artifacts(function(a) {
-      return { board: _.extend(a.board, { x: rect.x2 - a.board.w }) };
+      return { x: rect.x2 - a.w };
     });
   },
 
@@ -443,7 +435,7 @@ var SpacedeckBoardArtifacts = {
 
     var rect = this.artifact_selection_rect();
     this.update_selected_artifacts(function(a) {
-      return { board: _.extend(a.board, { y: rect.y2 - a.board.h }) };
+      return { y: rect.y2 - a.h };
     });
   },
 
@@ -453,7 +445,7 @@ var SpacedeckBoardArtifacts = {
     var rect = this.artifact_selection_rect();
     var cx = rect.x1 + (rect.x2-rect.x1)/2;
     this.update_selected_artifacts(function(a) {
-      return { board: _.extend(a.board, { x: cx - a.board.w/2 }) };
+      return { x: cx - a.w/2 };
     });
   },
 
@@ -463,7 +455,7 @@ var SpacedeckBoardArtifacts = {
     var rect = this.artifact_selection_rect();
     var cy = rect.y1 + (rect.y2-rect.y1)/2;
     this.update_selected_artifacts(function(a) {
-      return { board: _.extend(a.board, { y: cy - a.board.h/2 }) };
+      return { y: cy - a.h/2 };
     });
   },
 
@@ -473,11 +465,11 @@ var SpacedeckBoardArtifacts = {
     var arts = this.selected_artifacts();
     if (arts.length<2) return;
 
-    var totalw  = _.reduce(arts, function(sum, a) { return sum + a.board.w }, 0);
+    var totalw  = _.reduce(arts, function(sum, a) { return sum + a.w }, 0);
     var avgw    = totalw / arts.length;
 
     this.update_selected_artifacts(function(a) {
-      return { board: _.extend(a.board, { w: avgw }) };
+      return { w: avgw };
     });
   },
 
@@ -487,11 +479,11 @@ var SpacedeckBoardArtifacts = {
     var arts = this.selected_artifacts();
     if (arts.length<2) return;
 
-    var totalh  = _.reduce(arts, function(sum, a) { return sum + a.board.h }, 0);
+    var totalh  = _.reduce(arts, function(sum, a) { return sum + a.h }, 0);
     var avgh    = totalh / arts.length;
 
     this.update_selected_artifacts(function(a) {
-      return { board: _.extend(a.board, { h: avgh }) };
+      return { h: avgh };
     });
   },
 
@@ -506,16 +498,16 @@ var SpacedeckBoardArtifacts = {
     var selected = this.selected_artifacts();
     if (selected.length<3) return;
 
-    var sorted = _.sortBy(selected, function(a) { return a.board.x });
-    var startx = sorted[0].board.x + sorted[0].board.w/2;
-    var stopx  = _.last(sorted).board.x + _.last(sorted).board.w/2;
+    var sorted = _.sortBy(selected, function(a) { return a.x });
+    var startx = sorted[0].x + sorted[0].w/2;
+    var stopx  = _.last(sorted).x + _.last(sorted).w/2;
     var step   = (stopx-startx)/(sorted.length-1);
 
     for (var i=1; i<sorted.length-1; i++) {
       var a = sorted[i];
-      var x = startx + step*i - a.board.w/2;
+      var x = startx + step*i - a.w/2;
       this.update_artifacts([a],function(a) {
-        return { board: _.extend(a.board, {x: x}) }
+        return { x: x }
       });
     }
   },
@@ -526,16 +518,16 @@ var SpacedeckBoardArtifacts = {
     var selected = this.selected_artifacts();
     if (selected.length<3) return;
 
-    var sorted = _.sortBy(selected, function(a) { return a.board.y });
-    var starty = sorted[0].board.y + sorted[0].board.h/2;
-    var stopy  = _.last(sorted).board.y + _.last(sorted).board.h/2;
+    var sorted = _.sortBy(selected, function(a) { return a.y });
+    var starty = sorted[0].y + sorted[0].h/2;
+    var stopy  = _.last(sorted).y + _.last(sorted).h/2;
     var step   = (stopy-starty)/(sorted.length-1);
 
     for (var i=1; i<sorted.length-1; i++) {
       var a = sorted[i];
-      var y = starty + step*i - a.board.h/2;
+      var y = starty + step*i - a.h/2;
       this.update_artifacts([a],function(a) {
-        return { board: _.extend(a.board, {y: y}) }
+        return { y: y }
       });
     }
   },
@@ -546,21 +538,21 @@ var SpacedeckBoardArtifacts = {
     var selected = this.selected_artifacts();
     if (selected.length<3) return;
 
-    var sorted  = _.sortBy(selected, function(a) { return a.board.x });
-    var startx  = sorted[0].board.x;
-    var stopx   = _.last(sorted).board.x + _.last(sorted).board.w;
+    var sorted  = _.sortBy(selected, function(a) { return a.x });
+    var startx  = sorted[0].x;
+    var stopx   = _.last(sorted).x + _.last(sorted).w;
     var range   = stopx - startx;
-    var totalw  = _.reduce(sorted, function(sum, a) { return sum + a.board.w }, 0);
+    var totalw  = _.reduce(sorted, function(sum, a) { return sum + a.w }, 0);
     var avgs    = (range - totalw) / (sorted.length-1);
-    var prevend = startx + sorted[0].board.w;
+    var prevend = startx + sorted[0].w;
 
     for (var i=1; i<sorted.length-1; i++) {
       var a = sorted[i];
       var x = prevend + avgs;
       this.update_artifacts([a],function(a) {
-        return { board: _.extend(a.board, {x: x}) }
+        return { x: x }
       });
-      prevend = x+a.board.w;
+      prevend = x+a.w;
     }
   },
 
@@ -570,21 +562,21 @@ var SpacedeckBoardArtifacts = {
     var selected = this.selected_artifacts();
     if (selected.length<3) return;
 
-    var sorted  = _.sortBy(selected, function(a) { return a.board.y });
-    var starty  = sorted[0].board.y;
-    var stopy   = _.last(sorted).board.y + _.last(sorted).board.h;
+    var sorted  = _.sortBy(selected, function(a) { return a.y });
+    var starty  = sorted[0].y;
+    var stopy   = _.last(sorted).y + _.last(sorted).h;
     var range   = stopy - starty;
-    var totalh  = _.reduce(sorted, function(sum, a) { return sum + a.board.h }, 0);
+    var totalh  = _.reduce(sorted, function(sum, a) { return sum + a.h }, 0);
     var avgs    = (range - totalh) / (sorted.length-1);
-    var prevend = starty + sorted[0].board.h;
+    var prevend = starty + sorted[0].h;
 
     for (var i=1; i<sorted.length-1; i++) {
       var a = sorted[i];
       var y = prevend + avgs;
       this.update_artifacts([a],function(a) {
-        return { board: _.extend(a.board, {y: y}) }
+        return { y: y }
       });
-      prevend = y+a.board.h;
+      prevend = y+a.h;
     }
   },
 
@@ -594,20 +586,20 @@ var SpacedeckBoardArtifacts = {
     var selected = this.selected_artifacts();
     if (selected.length<2) return;
 
-    var sorted = _.sortBy(selected, function(a) { return a.board.x+a.board.y*this.active_space.advanced.width }.bind(this));
+    var sorted = _.sortBy(selected, function(a) { return a.x+a.y*this.active_space.advanced.width }.bind(this));
 
-    var minx = sorted[0].board.x;
-    var miny = sorted[0].board.y;
+    var minx = sorted[0].x;
+    var miny = sorted[0].y;
 
-    var sorted = _.sortBy(selected, function(a) { return -Math.max(a.board.w,a.board.h) }.bind(this));
+    var sorted = _.sortBy(selected, function(a) { return -Math.max(a.w,a.h) }.bind(this));
     
     var blocks = [];
 
     for (var i=0; i<sorted.length; i++) {
       var a = sorted[i];
       blocks.push({
-        w: a.board.w,
-        h: a.board.h,
+        w: a.w,
+        h: a.h,
         a: a
       });
     }
@@ -620,10 +612,10 @@ var SpacedeckBoardArtifacts = {
       if (block.fit) {
         var a = block.a;
         this.update_artifacts([a],function(a) {
-          return { board: _.extend(a.board, {
+          return {
             x: minx+block.fit.x,
             y: miny+block.fit.y
-          }) }
+          }
         });
       }
     }
diff --git a/public/javascripts/spacedeck_routes.js b/public/javascripts/spacedeck_routes.js
index eda4066986b408c5848e32795d74f5010fc93f57..e294358c51af39f5e92d517e7f495778e88de70e 100644
--- a/public/javascripts/spacedeck_routes.js
+++ b/public/javascripts/spacedeck_routes.js
@@ -170,7 +170,6 @@ var SpacedeckRoutes = {
               location.href = "/";
             } else {
               this.active_view = "account";
-              this.load_subscription();
             }
           }.bind(this)
         }
diff --git a/public/javascripts/spacedeck_sections.js b/public/javascripts/spacedeck_sections.js
index 99880362ac6ab349d299c68485c39ba61902fdcd..04afb538d500d46bfaff0d365c146957fd7130a5 100644
--- a/public/javascripts/spacedeck_sections.js
+++ b/public/javascripts/spacedeck_sections.js
@@ -369,8 +369,8 @@ var SpacedeckSections = {
       // canvas
       this.$watch('active_style.background_color', function (value, mutation) {
 
-        if (this.active_style.background_color != this.active_space.advanced.background_color) {
-          this.$set("active_space.advanced.background_color",this.active_style.background_color);
+        if (this.active_style.background_color != this.active_space.background_color) {
+          this.$set("active_space.background_color",this.active_style.background_color);
           this.throttled_save_active_space();
         }
 
@@ -448,7 +448,7 @@ var SpacedeckSections = {
 
         for (var i=0; i<props.length; i++) {
           var prop = props[i];
-          this.active_style[prop]=a.style[prop];
+          this.active_style[prop]=a[prop];
         }
 
         // defaults
@@ -457,10 +457,10 @@ var SpacedeckSections = {
         this.active_style.line_height = this.default_style.line_height;
         this.active_style.letter_spacing = this.default_style.letter_spacing;
 
-        this.active_style.padding_top =    a.style.padding_top || 0;
-        this.active_style.padding_bottom = a.style.padding_bottom || 0;
-        this.active_style.padding_left =   a.style.padding_left || 0;
-        this.active_style.padding_right =  a.style.padding_right || 0;
+        this.active_style.padding_top =    a.padding_top || 0;
+        this.active_style.padding_bottom = a.padding_bottom || 0;
+        this.active_style.padding_left =   a.padding_left || 0;
+        this.active_style.padding_right =  a.padding_right || 0;
 
         if (this.active_style.padding_top == this.active_style.padding_bottom) {
           this.active_style.padding_vert = this.active_style.padding_top;
@@ -476,10 +476,10 @@ var SpacedeckSections = {
           this.active_style.padding = this.active_style.padding_top;
         }
 
-        this.active_style.margin_top =    a.style.margin_top || 0;
-        this.active_style.margin_bottom = a.style.margin_bottom || 0;
-        this.active_style.margin_left =   a.style.margin_left || 0;
-        this.active_style.margin_right =  a.style.margin_right || 0;
+        this.active_style.margin_top =    a.margin_top || 0;
+        this.active_style.margin_bottom = a.margin_bottom || 0;
+        this.active_style.margin_left =   a.margin_left || 0;
+        this.active_style.margin_right =  a.margin_right || 0;
 
         if (this.active_style.margin_top == this.active_style.margin_bottom) {
           this.active_style.margin_vert = this.active_style.margin_top;
@@ -758,8 +758,8 @@ var SpacedeckSections = {
     },
 
     resize_minimap: function() {
-      if (!this.active_space || !this.active_space.advanced) return;
-      this.minimap_scale = this.active_space.advanced.width/100.0;
+      if (!this.active_space) return;
+      this.minimap_scale = this.active_space.width/100.0;
     },
 
     handle_minimap_mouseup: function(evt) {
@@ -921,7 +921,7 @@ var SpacedeckSections = {
 
     discover_zones: function() {
       this.zones = _.sortBy(_.filter(this.active_space_artifacts, function(a) { return (a.mime=="x-spacedeck/zone") }),
-        function(z){return z.style.order});
+        function(z){return z.order});
     },
 
     artifact_plaintext: function(a) {
@@ -1015,10 +1015,10 @@ var SpacedeckSections = {
       arts = _.filter(arts); // remove any nulls
 
       return {
-        x1: parseInt(_.min(arts.map(function(a){return ((!a.board || !a.board.x)?0:a.board.x)}))),
-        y1: parseInt(_.min(arts.map(function(a){return ((!a.board || !a.board.y)?0:a.board.y)}))),
-        x2: parseInt(_.max(arts.map(function(a){return (!a.board?0:a.board.x+a.board.w)}))),
-        y2: parseInt(_.max(arts.map(function(a){return (!a.board?0:a.board.y+a.board.h)})))
+        x1: parseInt(_.min(arts.map(function(a){return ((!a || !a.x)?0:a.x)}))),
+        y1: parseInt(_.min(arts.map(function(a){return ((!a || !a.y)?0:a.y)}))),
+        x2: parseInt(_.max(arts.map(function(a){return (!a?0:a.x+a.w)}))),
+        y2: parseInt(_.max(arts.map(function(a){return (!a?0:a.y+a.h)})))
       };
     },
 
@@ -1076,7 +1076,7 @@ var SpacedeckSections = {
       this.selection_metrics.count=arts.length;
       this.selection_metrics.scribble_selection = false;
       if (arts.length == 1 && arts[0].mime == "x-spacedeck/vector") {
-        if (arts[0].style.shape == "scribble") {
+        if (arts[0].shape == "scribble") {
           this.selection_metrics.scribble_selection = true;
         }
         this.selection_metrics.vector_points = arts[0].control_points;
@@ -1112,8 +1112,8 @@ var SpacedeckSections = {
     fixup_space_size: function() {
       if (!this.active_space) return;
 
-      this.active_space.advanced.width =Math.max(this.active_space.advanced.width, window.innerWidth);
-      this.active_space.advanced.height=Math.max(this.active_space.advanced.height, window.innerHeight);
+      this.active_space.width =Math.max(this.active_space.width, window.innerWidth);
+      this.active_space.height=Math.max(this.active_space.height, window.innerHeight);
     },
 
     end_transaction: function() {
@@ -1125,13 +1125,13 @@ var SpacedeckSections = {
       var er = this.enclosing_rect(this.active_space_artifacts);
       if (!er) return;
 
-      this.active_space.advanced.width =Math.max(er.x2+100, window.innerWidth);
-      this.active_space.advanced.height=Math.max(er.y2+100, window.innerHeight);
+      this.active_space.width =Math.max(er.x2+100, window.innerWidth);
+      this.active_space.height=Math.max(er.y2+100, window.innerHeight);
 
-      if (this._last_bounds_width != this.active_space.advanced.width ||
-        this._last_bounds_height != this.active_space.advanced.height) {
-        this._last_bounds_width = this.active_space.advanced.width;
-        this._last_bounds_height = this.active_space.advanced.height;
+      if (this._last_bounds_width != this.active_space.width ||
+        this._last_bounds_height != this.active_space.height) {
+        this._last_bounds_width = this.active_space.width;
+        this._last_bounds_height = this.active_space.height;
 
         save_space(this.active_space);
       }
@@ -1214,7 +1214,7 @@ var SpacedeckSections = {
 
       // this is a bit hacky, but might be the smartest place to do it
       if (a.view && a.view.vector_svg) {
-        a.style.shape_svg = a.view.vector_svg;
+        a.shape_svg = a.view.vector_svg;
       }
 
       window.artifact_save_queue[a._id] = a;
@@ -1329,7 +1329,7 @@ var SpacedeckSections = {
       this.update_selected_artifacts(function(a) {
         var c = {};
 
-        if (c[prop] != val) {
+        if (a[prop] != val) {
           //console.log("set_artifact_prop: ",c,val);
           c[prop]=val;
           return c;
@@ -1343,11 +1343,11 @@ var SpacedeckSections = {
       this.begin_transaction();
 
       this.update_selected_artifacts(function(a) {
-        var c = {style: a.style||{}};
-
-        if (c.style[prop] != val) {
+        var c = {};
+        
+        if (a[prop] != val) {
           //console.log("set_artifact_style_prop: ",c,val);
-          c.style[prop]=val;
+          c[prop]=val;
           return c;
         }
 
@@ -1419,7 +1419,7 @@ var SpacedeckSections = {
       if (this.selected_artifacts().length!=1 && this.opened_dialog!="background") return;
 
       if (this.opened_dialog=="background") {
-        this.active_style[this.color_picker_target] = this.active_space.advanced.background_color;
+        this.active_style[this.color_picker_target] = this.active_space.background_color;
       } else {
         if (!this.active_style[this.color_picker_target]) {
           this.active_style[this.color_picker_target] = this.default_style[this.color_picker_target];
@@ -1478,10 +1478,8 @@ var SpacedeckSections = {
 
       this.update_selected_artifacts(function(a) {
         return {
-          board: _.extend(a.board, {
-            x: a.board.x+dx,
-            y: a.board.y+dy
-          })
+          x: a.x+dx,
+          y: a.y+dy
         };
       });
     },
@@ -1489,7 +1487,7 @@ var SpacedeckSections = {
     /* -------------------------------------------------------------------- */
 
     highest_z: function() {
-      var z = _.max(this.active_space_artifacts.map(function(a){return a.board.z||0}));
+      var z = _.max(this.active_space_artifacts.map(function(a){return a.z||0}));
       if (z<0) z=0;
       if (z>999) z=999;
       return z;
@@ -1574,20 +1572,18 @@ var SpacedeckSections = {
         payload_thumbnail_web_uri: url || null,
         space_id: space._id,
 
-        style: {
-          order: this.active_space_artifacts.length+1,
-          valign: "middle",
-          align: "center"
-          //fill_color: "#f8f8f8"
-        }
+        order: this.active_space_artifacts.length+1,
+        valign: "middle",
+        align: "center"
+        //fill_color: "#f8f8f8"
       };
 
       if (mimes[item_type] == "text/html") {
-        new_item.style.padding_left = 10;
-        new_item.style.padding_top = 10;
-        new_item.style.padding_right = 10;
-        new_item.style.padding_bottom = 10;
-        new_item.style.fill_color = "rgba(255,255,255,1)";
+        new_item.padding_left = 10;
+        new_item.padding_top = 10;
+        new_item.padding_right = 10;
+        new_item.padding_bottom = 10;
+        new_item.fill_color = "rgba(255,255,255,1)";
         new_item.description = "<p>Text</p>";
       }
 
@@ -1600,13 +1596,11 @@ var SpacedeckSections = {
         z = point.z;
       }
 
-      new_item.board = {
-        x: parseInt(point.x),
-        y: parseInt(point.y),
-        w: w,
-        h: h,
-        z: z
-      };
+      new_item.x = parseInt(point.x);
+      new_item.y = parseInt(point.y);
+      new_item.z = z;
+      new_item.w = w;
+      new_item.h = h;
 
       if (this.guest_nickname) {
         new_item.editor_name = this.guest_nickname;
@@ -1665,7 +1659,7 @@ var SpacedeckSections = {
       for (var i=0; i<new_zones.length; i++) {
         if (new_zones[i]) {
           if (!new_zones[i].style) new_zones[i].style = {};
-          new_zones[i].style.order = i;
+          new_zones[i].order = i;
           save_artifact(new_zones[i]);
         }
       }
@@ -1679,7 +1673,7 @@ var SpacedeckSections = {
       for (var i=0; i<new_zones.length; i++) {
         if (new_zones[i]) {
           if (!new_zones[i].style) new_zones[i].style = {};
-          new_zones[i].style.order = i;
+          new_zones[i].order = i;
           save_artifact(new_zones[i]);
         }
       }
@@ -1695,17 +1689,13 @@ var SpacedeckSections = {
         space_id: this.active_space._id,
         mime: "x-spacedeck/zone",
         description: "Zone "+(this.zones.length+1),
-        board: {
-          x: point.x,
-          y: point.y,
-          w: w,
-          h: h,
-          z: 0
-        },
-        style: {
-          valign: "middle",
-          align: "center"
-        }
+        x: point.x,
+        y: point.y,
+        w: w,
+        h: h,
+        z: 0,
+        valign: "middle",
+        align: "center"
       };
 
       if (this.guest_nickname) {
@@ -1735,22 +1725,18 @@ var SpacedeckSections = {
         space_id: this.active_space._id,
         mime: "x-spacedeck/shape",
         description: "Text",
-        board: {
-          x: point.x,
-          y: point.y,
-          z: point.z,
-          w: w,
-          h: h
-        },
-        style: {
-          stroke_color: "#ffffff",
-          text_color: "#ffffff",
-          stroke: 0,
-          fill_color: "#000000",
-          shape: shape_type,
-          valign: "middle",
-          align: "center"
-        }
+        x: point.x,
+        y: point.y,
+        z: point.z,
+        w: w,
+        h: h,
+        stroke_color: "#ffffff",
+        text_color: "#ffffff",
+        stroke: 0,
+        fill_color: "#000000",
+        shape: shape_type,
+        valign: "middle",
+        align: "center"
       };
 
       if (this.guest_nickname) {
@@ -1829,17 +1815,13 @@ var SpacedeckSections = {
         state: "uploading",
         payload_thumbnail_medium_uri: null,
         payload_thumbnail_web_uri: null,
-        board: {
-          x: point.x,
-          y: point.y,
-          w: w,
-          h: h,
-          z: point.z
-        },
-        style: {
-          order: this.active_space_artifacts.length+1,
-          fill_color: fill
-        }
+        x: point.x,
+        y: point.y,
+        w: w,
+        h: h,
+        z: point.z,
+        order: this.active_space_artifacts.length+1,
+        fill_color: fill
       }
 
       this.update_board_artifact_viewmodel(a);
@@ -1864,7 +1846,11 @@ var SpacedeckSections = {
           a.payload_thumbnail_big_uri = updated_a.payload_thumbnail_big_uri;
           a.payload_alternatives = updated_a.payload_alternatives;
           a.mime = updated_a.mime;
-          a.board = updated_a.board;
+          a.x = updated_a.x;
+          a.y = updated_a.y;
+          a.w = updated_a.w;
+          a.h = updated_a.h;
+          a.z = updated_a.z;
           a.state = updated_a.state;
           this.update_board_artifact_viewmodel(a);
 
@@ -2002,26 +1988,26 @@ var SpacedeckSections = {
     clear_formatting_walk: function(el,cmd,arg1,arg2) {
       if (el && el.style) {
         if (cmd == "preciseFontSize") {
-          el.style.fontSize = null;
+          el.fontSize = null;
         } else if (cmd == "letterSpacing") {
-          el.style.letterSpacing = null;
+          el.letterSpacing = null;
         } else if (cmd == "lineHeight") {
-          el.style.lineHeight = null;
+          el.lineHeight = null;
         } else if (cmd == "fontName") {
-          el.style.fontFamily = null;
+          el.fontFamily = null;
         } else if (cmd == "fontWeight") {
-          el.style.fontWeight = null;
-          el.style.fontStyle = null;
+          el.fontWeight = null;
+          el.fontStyle = null;
         } else if (cmd == "bold") {
-          el.style.fontWeight = null;
+          el.fontWeight = null;
         } else if (cmd == "italic") {
-          el.style.fontStyle = null;
+          el.fontStyle = null;
         } else if (cmd == "underline") {
-          el.style.textDecoration = null;
+          el.textDecoration = null;
         } else if (cmd == "strikeThrough") {
-          el.style.textDecoration = null;
+          el.textDecoration = null;
         } else if (cmd == "forecolor") {
-          el.style.color = null;
+          el.color = null;
         }
       }
 
@@ -2108,6 +2094,9 @@ var SpacedeckSections = {
 
           if (a.description!=dom.innerHTML) {
             a.description = dom.innerHTML;
+
+            console.log("new DOM:",dom.innerHTML);
+            
             this.update_board_artifact_viewmodel(a);
             this.queue_artifact_for_save(a);
 
@@ -2141,10 +2130,7 @@ var SpacedeckSections = {
 
     remove_link_from_selected_artifacts: function() {
       this.update_selected_artifacts(function(a) {
-        var meta = a.meta || {};
-        delete meta.link_uri;
-
-        return {meta: meta};
+        return {link_uri: ""};
       });
     },
 
@@ -2160,9 +2146,7 @@ var SpacedeckSections = {
       var insert_link_url = prompt("URL:",def);
 
       this.update_selected_artifacts(function(a) {
-        var meta = a.meta || {};
-        meta.link_uri = insert_link_url;
-        var update = {meta: meta};
+        var update = {link_uri: insert_link_url};
 
         if (a.payload_uri && a.payload_uri.match("webgrabber")) {
           var enc_uri = encodeURIComponent(btoa(insert_link_url));
@@ -2185,11 +2169,10 @@ var SpacedeckSections = {
       delete copy["$index"];
       delete copy["_id"];
 
-      if (dx) copy.board.x += dx;
-      if (dy) copy.board.y += dy;
+      if (dx) copy.x += dx;
+      if (dy) copy.y += dy;
 
-      if (!copy.style) copy.style = {};
-      copy.style.order = this.active_space_artifacts.length+1;
+      copy.order = this.active_space_artifacts.length+1;
 
       if (this.guest_nickname) {
         copy.editor_name = this.guest_nickname;
@@ -2334,16 +2317,16 @@ var SpacedeckSections = {
             if (parsed[i].mime) {
               var z = this.highest_z()+1;
               if (parsed.length==1) {
-                var w = parsed[i].board.w;
-                var h = parsed[i].board.h;
+                var w = parsed[i].w;
+                var h = parsed[i].h;
                 var point = this.find_place_for_item(w,h);
-                parsed[i].board.x = point.x;
-                parsed[i].board.y = point.y;
-                parsed[i].board.z = point.z;
+                parsed[i].x = point.x;
+                parsed[i].y = point.y;
+                parsed[i].z = point.z;
               } else {
-                parsed[i].board.x = parsed[i].board.x+50;
-                parsed[i].board.y = parsed[i].board.y+50;
-                parsed[i].board.y = parsed[i].board.z+z;
+                parsed[i].x = parsed[i].x+50;
+                parsed[i].y = parsed[i].y+50;
+                parsed[i].y = parsed[i].z+z;
               }
               this.clone_artifact(parsed[i], 0,0, function(a) {
                 this.multi_select([a]);
@@ -2373,13 +2356,11 @@ var SpacedeckSections = {
       var h = 300;
       var point = this.find_place_for_item(w,h);
 
-      new_item.board = {
-        x: point.x,
-        y: point.y,
-        w: w,
-        h: h,
-        z: point.z
-      };
+      new_item.x = point.x;
+      new_item.y = point.y;
+      new_item.w = w;
+      new_item.h = h;
+      new_item.z = point.z;
 
       if (this.guest_nickname) {
         new_item.editor_name = this.guest_nickname;
@@ -2402,16 +2383,12 @@ var SpacedeckSections = {
         mime: "image/png",
         description: url,
         state: "uploading",
-        board: {
-          x: point.x,
-          y: point.y,
-          w: 200,
-          h: 200,
-          z: z
-        },
-        style: {
-          order: this.active_space_artifacts.length
-        }
+        x: point.x,
+        y: point.y,
+        w: 200,
+        h: 200,
+        z: z,
+        order: this.active_space_artifacts.length
       }
 
       var metadata = parse_link(url)
@@ -2473,16 +2450,12 @@ var SpacedeckSections = {
         payload_thumbnail_medium_uri: metadata.thumbnail_url,
         payload_thumbnail_web_uri: metadata.thumbnail_url,
         state: "idle",
-        meta: {
-          title: metadata.title,
-          link_uri: metadata.url || url
-        },
-        board: {
-          x: point.x - w/2,
-          y: point.y - h/2,
-          w: w,
-          h: h
-        }
+        title: metadata.title,
+        link_uri: metadata.url || url,
+        x: point.x - w/2,
+        y: point.y - h/2,
+        w: w,
+        h: h
       });
 
       if (this.guest_nickname) {
@@ -2591,7 +2564,7 @@ var SpacedeckSections = {
     },
 
     remove_section_background: function() {
-      this.active_space.advanced.background_uri = null;
+      this.active_space.background_uri = null;
       save_space(this.active_space);
     },
 
@@ -2652,8 +2625,8 @@ var SpacedeckSections = {
 
       this.bounds_zoom = this.viewport_zoom;
 
-      var eff_w = this.active_space.advanced.width*this.viewport_zoom;
-      var eff_h = this.active_space.advanced.height*this.viewport_zoom;
+      var eff_w = this.active_space.width*this.viewport_zoom;
+      var eff_h = this.active_space.height*this.viewport_zoom;
 
       if (window.innerWidth>eff_w) {
         // horizontal centering
@@ -2846,8 +2819,8 @@ var SpacedeckSections = {
         
         var el = $("#space")[0];
 
-        var eff_w = this.active_space.advanced.width*this.viewport_zoom;
-        var eff_h = this.active_space.advanced.height*this.viewport_zoom;
+        var eff_w = this.active_space.width*this.viewport_zoom;
+        var eff_h = this.active_space.height*this.viewport_zoom;
         
         var sx = el.scrollLeft;
         var sy = el.scrollTop;
@@ -2980,9 +2953,9 @@ var SpacedeckSections = {
 
           var w = 300;
           var h = 200;
-          if (parsed.board && parsed.board.w && parsed.board.h) {
-            w = parsed.board.w;
-            h = parsed.board.h;
+          if (parsed.board && parsed.w && parsed.h) {
+            w = parsed.w;
+            h = parsed.h;
           }
 
           var point = this.cursor_point_to_space(evt);
diff --git a/public/javascripts/spacedeck_spaces.js b/public/javascripts/spacedeck_spaces.js
index 55526ce375ad1ec49f2053801f6285633f3c5596..b528b8327ed030afa8af4e1d8e19f4f7ea8dfba7 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 59a66d2f79564c3374c80058522a3d5ee74f5f6e..4cbd60d32a3a6051bda4da5c6f3dfa2e299653ba 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 ead97d76e62b88c930e385d148b6bdb09f3f9193..141cb1ae0d5d4f060fa8b3147a70254bd641bb0d 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 c6ffd2e0b44afbb12c2792bc00ae8b480096cf36..9bdb4ec962e6fb7dbd10269db3ab50730e3cb33a 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 (xd2<xd1) {
@@ -469,10 +469,10 @@ function setup_whiteboard_directives() {
 
           if (snap_middle) {
             var xd = xd3;
-            var sx = r[0].x+a.board.w/2;
+            var sx = r[0].x+a.w/2;
 
             var yd = yd3;
-            var sy = r[0].y+a.board.h/2;
+            var sy = r[0].y+a.h/2;
           }
 
           return [[xd,sx],[yd,sy]];
@@ -531,18 +531,14 @@ function setup_whiteboard_directives() {
         mime: "x-spacedeck/vector",
         description: "",
         control_points: [{dx:0,dy:0}],
-        board: {
-          x: point.x,
-          y: point.y,
-          z: z,
-          w: 64,
-          h: 64
-        },
-        style: {
-          stroke_color: "#000000",
-          stroke: 2,
-          shape: "scribble"
-        }
+        x: point.x,
+        y: point.y,
+        z: z,
+        w: 64,
+        h: 64,
+        stroke_color: "#000000",
+        stroke: 2,
+        shape: "scribble"
       };
 
       $scope.save_artifact(a, function(saved_a) {
@@ -572,18 +568,14 @@ function setup_whiteboard_directives() {
         mime: "x-spacedeck/vector",
         description: "",
         control_points: [{dx:0,dy:0},{dx:0,dy:0},{dx:0,dy:0}],
-        board: {
-          x: point.x,
-          y: point.y,
-          z: z,
-          w: 64,
-          h: 64
-        },
-        style: {
-          stroke_color: "#000000",
-          stroke: 2,
-          shape: "arrow"
-        }
+        x: point.x,
+        y: point.y,
+        z: z,
+        w: 64,
+        h: 64,
+        stroke_color: "#000000",
+        stroke: 2,
+        shape: "arrow"
       };
 
       $scope.save_artifact(a, function(saved_a) {
@@ -612,18 +604,14 @@ function setup_whiteboard_directives() {
         mime: "x-spacedeck/vector",
         description: "",
         control_points: [{dx:0,dy:0},{dx:0,dy:0}],
-        board: {
-          x: point.x,
-          y: point.y,
-          z: z,
-          w: 64,
-          h: 64
-        },
-        style: {
-          stroke_color: "#000000",
-          stroke: 2,
-          shape: "line"
-        }
+        x: point.x,
+        y: point.y,
+        z: z,
+        w: 64,
+        h: 64,
+        stroke_color: "#000000",
+        stroke: 2,
+        shape: "line"
       };
 
       $scope.save_artifact(a, function(saved_a) {
@@ -675,11 +663,11 @@ function setup_whiteboard_directives() {
 
           if (_.include(["text","placeholder"],$scope.artifact_major_type(ars[i]))) {
             // some types of artifact need a minimum size
-            if (ars[i].board.w<10) {
-              ars[i].board.w = 10;
+            if (ars[i].w<10) {
+              ars[i].w = 10;
             }
-            if (ars[i].board.h<10) {
-              ars[i].board.h = 10;
+            if (ars[i].h<10) {
+              ars[i].h = 10;
             }
           }
 
@@ -827,10 +815,8 @@ function setup_whiteboard_directives() {
 
           if (old_a) {
             return {
-              board: _.extend(a.board, {
-                x: old_a.board.x + dx - snap_dx,
-                y: old_a.board.y + dy - snap_dy
-              })
+              x: old_a.x + dx - snap_dx,
+              y: old_a.y + dy - snap_dy
             };
           } else {
             // deleted?
@@ -865,26 +851,24 @@ function setup_whiteboard_directives() {
 
         var scale_x = lead_x ? (moved_x)/lead_x : 1;
         var scale_y = lead_y ? (moved_y)/lead_y : 1;
-        if ($scope.transform_lock) scale_y = scale_x;
+        if (!$scope.transform_lock) scale_y = scale_x;
 
         $scope.update_selected_artifacts(function(a) {
           var old_a = $scope.find_artifact_before_transaction(a);
 
-          var x1 = origin_x + ((old_a.board.x - origin_x) * scale_x);
-          var y1 = origin_y + ((old_a.board.y - origin_y) * scale_y);
-          var x2 = origin_x + (((old_a.board.x + old_a.board.w) - origin_x) * scale_x);
-          var y2 = origin_y + (((old_a.board.y + old_a.board.h) - origin_y) * scale_y);
+          var x1 = origin_x + ((old_a.x - origin_x) * scale_x);
+          var y1 = origin_y + ((old_a.y - origin_y) * scale_y);
+          var x2 = origin_x + (((old_a.x + old_a.w) - origin_x) * scale_x);
+          var y2 = origin_y + (((old_a.y + old_a.h) - origin_y) * scale_y);
 
           if (x1>x2) { var t = x1; x1 = x2; x2 = t; }
           if (y1>y2) { var t = y1; y1 = y2; y2 = t; }
 
           return {
-            board: _.extend(a.board, {
-              x: x1,
-              y: y1,
-              w: x2 - x1,
-              h: y2 - y1
-            })
+            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;
+      if (artifact.w < 0) bshiftx = -artifact.w;
+      if (artifact.h < 0) bshifty = -artifact.h;
 
-      var shifted_board = {
-        x: board.x + bshiftx - shiftx,
-        y: board.y + bshifty - shifty,
+      return {
+        x: artifact.x + bshiftx - shiftx,
+        y: artifact.y + bshifty - shifty,
         w: w,
         h: h,
-        z: board.z
-      };
-
-      return {
-        board: shifted_board,
+        z: artifact.z,
         control_points: shifted_cps
       };
     }
diff --git a/public/javascripts/vector-render.js b/public/javascripts/vector-render.js
index 84adb917ec513bee21b48193954e69f64107d04f..4d4bc9f8e247e8c5c36e71bcf7cc5d866c09235a 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  = "<defs><marker id='ae" + markerId + "' refX=\"0.1\" refY=\"3\" markerWidth=\"3\" markerHeight=\"6\" orient=\"auto\">";
-        tip += "<path d=\"M-3,0 V6 L3,3 Z\" fill=\""+a.style.stroke_color+"\" stroke-width=\"0\"/></marker></defs>";
-    var svg = tip + "<path d='" + d + "' style='stroke-width:" + a.style.stroke + ";' marker-end='url(#ae" + markerId + ")'/>";
+        tip += "<path d=\"M-3,0 V6 L3,3 Z\" fill=\""+a.stroke_color+"\" stroke-width=\"0\"/></marker></defs>";
+    var svg = tip + "<path d='" + d + "' style='stroke-width:" + a.stroke + ";' marker-end='url(#ae" + markerId + ")'/>";
 
     return svg;
   }
@@ -237,11 +237,11 @@ function render_vector_rect(xradius,yradius,offset) {
 }
 
 function render_vector_shape(a) {
-  var stroke = parseInt(a.style.stroke) + 4;
+  var stroke = parseInt(a.stroke) + 4;
   var offset = stroke / 2;
 
-  var xr = (a.board.w-stroke) / 2;
-  var yr = (a.board.h-stroke) / 2;
+  var xr = (a.w-stroke) / 2;
+  var yr = (a.h-stroke) / 2;
 
   var shape_renderers = {
     ellipse: function() { return render_vector_ellipse(xr, yr, offset); },
@@ -258,7 +258,7 @@ function render_vector_shape(a) {
     cloud: function() { return render_vector_cloud(xr, yr, offset); },
   }
 
-  var render_func = shape_renderers[a.style.shape];
+  var render_func = shape_renderers[a.shape];
 
   if (!render_func) return "";
 
diff --git a/routes/api/memberships.js b/routes/api/memberships.js
index f01c0e702477add8d627c2c5e3750124eea306bd..f25cd1db0ef4c362deab8132329afa13083da09c 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 054e49ce27032828269bdee802d2001204a93ebb..8c2fdba1280c25b84ac1382f2b47622a0ee7da4d 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 = [];
+  if (!data.email || !data.password) {
+    res.status(400).json({});
+    return;
+  }
+  
+  var email = req.body.email.toLowerCase();
+  var password = req.body["password"];
 
-              user.sessions.push(session);
+  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);
 
-              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";
+          var session = {
+            user_id: user._id,
+            token: token,
+            ip: req.ip,
+            device: "web",
+            created_at: new Date()
+          };
 
-                res.cookie('sdsession', token, { domain: domain, httpOnly: true });
-                res.status(201).json(session);
-              });
+          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);
-          }
-        } else {
-          res.sendStatus(404);
-        }
+        });
+      } else {
+        res.sendStatus(403);
       }
-    }));
-  } else {
-    res.status(400).json({});
-  }
+    });
 });
 
 router.delete('/current', function(req, res, next) {
   if (req.user) {
-    var user = req.user;
+    /*var user = req.user;
     var newSessions = user.sessions.filter( function(session){
       return session.token != req.token;
-    });
-    user.sessions = newSessions;
-    user.save(function(err, result) {
+    });*/
+    //user.sessions = newSessions;
+    //user.save(function(err, result) {
       var domain = new URL(config.get('endpoint')).hostname;
       res.clearCookie('sdsession', { domain: domain });
       res.sendStatus(204);
-    });
+    //});
   } else {
     res.sendStatus(404);
   }
diff --git a/routes/api/space_artifacts.js b/routes/api/space_artifacts.js
index d3e5006c091cf106e5a27e64a8e729ca75c0a33a..c11b8ce7ac2c3ca75572a72eb4563d8861ef48d2 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 cee355773d84c41c965f4551ddca11aba70d14f8..cbdf4909b37bfcd612a5c2118af4b7d78c0b023c 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 3a28f71cd5881544fc559fa2bfc08d6163340611..2b7b15bd614a15b5bcdc37975ab1fd5ba547c762 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 7ec7b73e62b6ee9e923671656fedd0824c4e2054..2b88dfaba5f1f34ed2fca2488dbc98c4e909c49e 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;
-
-            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);
+        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);
         });
 
       });
@@ -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 47e4a58955657cb436d297f4cae6c83e836012d3..400b78d719ae1e764f1f7acfc2c175ef97e252e9 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;
-
-      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.");
-            }
-          });
-        });
+  var msg = attrs;
+  msg._id = uuidv4();
 
-      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 ff0545d584c8fa47b2d95550d9dba9fc122488ae..57f57041425f836ee798804bbe5a8d73141f70af 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")
-        };
-
-        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;
-            });
+        // 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']};
+
+        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
-        }, {
-          "$set": {
-            advanced: adv
-          }
+        db.Space.update({
+          background_uri: backgroundUrl
         }, {
-          "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 da46a59e8eabdae03feba3ca5d89de197e0cbaeb..0000000000000000000000000000000000000000
--- 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 7e1a1aa0287da723caab962e023cebd079b01c70..f389bf9817b2af3992bcb60a31f9dacac68e4e48 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;
   }
-});
-
-router.get('/oauth2callback/url', function(req, res) {
-  var google = require('googleapis');
-  var OAuth2 = google.auth.OAuth2;
-
-  var oauth2Client = new OAuth2(
-    config.google_access,
-    config.google_secret,
-    config.endpoint + "/login"
-  );
-
-  var url = oauth2Client.generateAuthUrl({
-    access_type: 'online',
-    scope: "email"
-  });
-
-  res.status(200).json({"url":url});
-});
-
-router.get('/loginorsignupviagoogle', function(req, res) {
-  var google = require('googleapis');
-  var OAuth2 = google.auth.OAuth2;
-  var plus = google.plus('v1');
-
-  var oauth2Client = new OAuth2(
-    config.google_access,
-    config.google_secret,
-    config.endpoint + "/login"
-  );
-
-  var loginUser = function(user, cb) {
-    crypo.randomBytes(48, function(ex, buf) {
-      var token = buf.toString('hex');
-      var session = {
-        token: token,
-        created_at: new Date()
-      };
-      if(!user.sessions)
-        user.sessions = [];
-      user.sessions.push(session);
-      user.save(function(err, user) {
-        cb(session);
-      });
-    });
-  };
-
-  var code = req.query.code;
-  oauth2Client.getToken(code, function(err, tokens) {
-
-    if (err) res.status(400).json(err);
-    else {
-      var apiUrl = "https://www.googleapis.com/oauth2/v1/userinfo?alt=json&access_token=" + tokens.access_token;
-      
-      var finalizeLogin = function(session){
-        res.cookie('sdsession', session.token, { httpOnly: true });
-        res.status(201).json(session);
-      };
-
-      request.get(apiUrl, function(error, response, body) {
-        if (error) res.status(400).json(error);
-        else {
-          const data = JSON.parse(body);
-          const email = data.email;
-          const name = data.name;
-
-          User.findOne({email: email}, function (err, user) {
-            if (user) {
-              // login new google user
-              if (user.account_type == "google") {
-                // just login
-                loginUser(user, (session) => {
-                  finalizeLogin(session);
-                });
-              } else {
-                res.status(400).json({"error":"user_email_already_used"});
-              }
-            } else {
-              const u = new User({
-                email: email,
-                account_type: "google",
-                nickname: name,
-                avatar_thumb_uri: body.picture,
-                preferences: {
-                  language: req.i18n.locale
-                },
-                confirmed_at: new Date()
-              });
-
-              u.save(function (err) {
-                if (err) res.status(400).json(err);
-                else {
-                  var homeSpace = new Space({
-                    name: req.i18n.__("home"),
-                    space_type: "folder",
-                    creator: u
-                  });
-
-                  homeSpace.save(function(err, homeSpace) {
-                    if (err) res.status(400).json(err);
-                    else {
-                      u.home_folder_id = homeSpace._id;
-                      u.save(function(err){
-                        if (err) res.sendStatus(400);
-                        else {
-
-                          mailer.sendMail(u.email, req.i18n.__("welcome_subject"), req.i18n.__("welcome_body"), {});
-                          loginUser(u, function(session) {
-                            finalizeLogin(session);
-                          });
+  
+  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) {
+    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 u = {
+            _id: uuidv4(),
+            email: email,
+            account_type: "email",
+            nickname: nickname,
+            password_hash: hash,
+            prefs_language: req.i18n.locale,
+            confirmation_token: token
+          };
+
+          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);
+                    });
+                })
+            });
+        });
       });
-    }
-  });
+    });
+  };
+  
+  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 519d9aa9736c235fd0d0ddb892b7b0840c555a9c..17ab0f83a5526a7a54e4268e00b088ea8cf08958 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 1169b69296e9d77de6f37b46bd7c4b5633c7b2cb..7819b1bdd81f6398d9f22141cac3eee954ff3103 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 0000000000000000000000000000000000000000..6b841d17625ded27b0daa7080e2311dac5245c08
--- /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 ff70cfcb9c4b576c02e042a680db00abc646411e..7aeaf81cfce50fb0ff0eac75f21a7c54db3628d5 100644
--- a/views/partials/account.html
+++ b/views/partials/account.html
@@ -1,6 +1,6 @@
 <div id="team" class="dialog in" style="padding:100px;z-index:20000;position:absolute;width:100%;min-height:100%;background-color:#eee" v-if="active_view == 'account' && user" v-cloak>
 
-  <a href="/spaces" class="btn btn-round btn-icon btn-stroke-darken btn-md pull-right" style="position:absolute;top:30px;right:30px"><span class="icon icon-cross-0"></span></a>
+  <a href="/spaces" class="btn btn-round btn-icon btn-dark btn-md pull-right" style="position:absolute;top:30px;right:30px"><span class="icon icon-cross-0"></span></a>
 
   <div class="dialog-tabs" style="margin:auto">
     <div class="dialog-tab" v-bind:class="{open:account=='profile'}" v-on:click="account='profile'"><span>[[__("profile_caption")]]</span></div>
@@ -80,21 +80,29 @@
             <span class="icon icon-check"></span> <span>[[__("confirmation_sent_another")]]</span>
           </p>
         </div>
+
+        <div class="form-group">
+          <label class="label">Spacedeck.com Data Import</label>
+          <p v-if="!importables">No .ZIP files found in Spacedeck application folder.</p>
+          <ul>
+            <li v-for="f in importables">{{f}} <button v-on:click="start_zip_import(f)">Start Import</button></li>
+          </ul>
+        </div>
       </div>
     </div>
 
     <div class="collapse" v-bind:class="{in:account=='language'}">
       <div class="modal-section">
         <label class="radio" v-bind:class="{checked
-        : user.preferences.language=='en'}" v-on:click="save_user_language('en')">
+        : user.prefs_language=='en'}" v-on:click="save_user_language('en')">
           <input type="radio" id="user-preferences_language" name="language" value="en"><span>English</span>
         </label>
         <hr/>
-        <label class="radio" v-bind:class="{checked: user.preferences.language=='de'}" v-on:click="save_user_language('de')">
+        <label class="radio" v-bind:class="{checked: user.prefs_language=='de'}" v-on:click="save_user_language('de')">
           <input type="radio" id="user-preferences_language" name="language" value="de"><span>Deutsch</span>
         </label>
         <hr/>
-        <label class="radio" v-bind:class="{checked: user.preferences.language=='fr'}" v-on:click="save_user_language('fr')">
+        <label class="radio" v-bind:class="{checked: user.prefs_language=='fr'}" v-on:click="save_user_language('fr')">
           <input type="radio" id="user-preferences_language" name="language" value="fr"><span>Français</span>
         </label>
       </div>
@@ -104,8 +112,8 @@
       <div class="modal-section labels-inline">
         <div class="form-group">
           <label class="checkbox"
-            v-bind:class="{checked: user.preferences.email_notifications}"
-            v-on:click="account_save_user_notifications(!user.preferences.email_notifications);">
+            v-bind:class="{checked: user.prefs_email_notifications}"
+            v-on:click="account_save_user_notifications(!user.prefs_email_notifications);">
             <span>[[__('notifications_option_chat')]]</span>
           </label>
         </div>
diff --git a/views/partials/space-isolated.html b/views/partials/space-isolated.html
index a7b8efbf79e61768126030303ce621957270cab3..6ff01072a09d803cbe465f105b0772eaeb19e6aa 100644
--- a/views/partials/space-isolated.html
+++ b/views/partials/space-isolated.html
@@ -1,9 +1,9 @@
 <div id="space" class="section board active mouse-{{mouse_state}} tool-{{active_tool}}">
 
-  <div class="space-bounds" style="width:{{active_space.advanced.width*bounds_zoom}}px;height:{{active_space.advanced.height*bounds_zoom}}px;"></div>
+  <div class="space-bounds" style="width:{{active_space.width*bounds_zoom}}px;height:{{active_space.height*bounds_zoom}}px;"></div>
 
   <div class="wrapper"
-    style="transform:scale({{viewport_zoom}});transform-origin:0 0;width:{{active_space.advanced.width}}px;height:{{active_space.advanced.height}}px;background-image:url('{{active_space.advanced.background_uri}}');background-color:{{active_space.advanced.background_color}};margin-left:{{bounds_margin_horiz}}px;margin-top:{{bounds_margin_vert}}px" >
+    style="transform:scale({{viewport_zoom}});transform-origin:0 0;width:{{active_space.width}}px;height:{{active_space.height}}px;background-image:url('{{active_space.background_uri}}');background-color:{{active_space.background_color}};margin-left:{{bounds_margin_horiz}}px;margin-top:{{bounds_margin_vert}}px" >
 
     <div v-repeat="a : active_space_artifacts"
       v-class="text-editing:(editing_artifact_id==a._id && (a.view.major_type=='text' || a.view.major_type=='shape'))"
@@ -81,7 +81,7 @@
           </div>
 
           <div class="tl-controls">
-            <div class="btn btn-md btn-toggle btn-round" v-class="alt:a.player_view.state=='playing'" v-show="a.board.w>=200 || a.player_view.state!='playing'">
+            <div class="btn btn-md btn-toggle btn-round" v-class="alt:a.player_view.state=='playing'" v-show="a.w>=200 || a.player_view.state!='playing'">
               <span class="btn-option play">
                 <span class="icon icon-controls-play"></span>
               </span>
@@ -95,8 +95,8 @@
               <span class="icon icon-controls-stop"></span>
             </span>
 
-            <span class="tl-title" v-show="a.board.w>=250 && a.board.h>=150">{{a.view.filename}}</span>
-            <span class="tl-times" class="btn-group" v-show="a.board.w>=400 && a.board.h>=150">
+            <span class="tl-title" v-show="a.w>=250 && a.h>=150">{{a.view.filename}}</span>
+            <span class="tl-times" class="btn-group" v-show="a.w>=400 && a.h>=150">
               <span class="btn btn-md btn-transparent no-p set-inpoint">{{a.player_view.current_time_string}} /</span>
               <span class="btn btn-md btn-transparent no-p set-outpoint">{{a.player_view.total_time_string}}</span>
             </span>
diff --git a/views/partials/space.html b/views/partials/space.html
index 208173eca7b13ad5b76bb6f22f882eb559daa29f..3b7482f1bad65ce6d8529b2f78d273b1de753ade 100644
--- a/views/partials/space.html
+++ b/views/partials/space.html
@@ -198,7 +198,7 @@
 <div id="space" v-cloak
      v-if="active_view == 'space' && active_space_loaded"
      class="section board active mouse-{{mouse_state}} tool-{{active_tool}}"
-     v-bind:style="{'background-color': active_space.advanced.background_color}"
+     v-bind:style="{'background-color': active_space.background_color}"
      v-sd-droppable="handle_data_drop;active_space"
      v-sd-whiteboard
      v-on:scroll="handle_scroll"
@@ -206,16 +206,16 @@
 
   <div id="space-clipboard" style="position:fixed;top:0;left:0;z-index:0;opacity:0;background-color:white"><textarea v-model="selected_artifacts_json" cols="2" rows="2" id="clipboard-ta" class="mousetrap"></textarea></div>
 
-  <div class="space-bounds" v-bind:style="{width: active_space.advanced.width*bounds_zoom + 'px', height: active_space.advanced.height*bounds_zoom + 'px', 'background-color': active_space.advanced.background_color}"></div>
+  <div class="space-bounds" v-bind:style="{width: active_space.width*bounds_zoom + 'px', height: active_space.height*bounds_zoom + 'px', 'background-color': active_space.background_color}"></div>
 
   <div class="wrapper"
        v-bind:style="{
                 transform: 'scale('+viewport_zoom+')',
                 'transform-origin': '0 0',
-                width: active_space.advanced.width + 'px',
-                height: active_space.advanced.height + 'px',
-                'background-image': (active_space.advanced.background_uri)?'url(' + active_space.advanced.background_uri + ')':'',
-                'background-color': ''+active_space.advanced.background_color,
+                width: active_space.width + 'px',
+                height: active_space.height + 'px',
+                'background-image': (active_space.background_uri)?'url(' + active_space.background_uri + ')':'',
+                'background-color': ''+active_space.background_color,
                 'margin-left': bounds_margin_horiz + 'px',
                 'margin-top': bounds_margin_vert + 'px'}" >
 
@@ -331,7 +331,7 @@
             <source v-bind:src="a.payload_uri" v-bind:type="a.mime" v-if="a.payload_uri"/>
           </audio>
 
-          <div class="timeline" v-show="a.board.h>=64 && a.board.w>=170" v-bind:style="{'background-image': 'url(' + a.payload_thumbnail_web_uri +')'}">
+          <div class="timeline" v-show="a.h>=64 && a.w>=170" v-bind:style="{'background-image': 'url(' + a.payload_thumbnail_web_uri +')'}">
             <div class="tl-current-time" v-bind:style="{width: a.player_view.current_time_float*100 + '%'}"></div>
             <div class="tl-inpoint" v-bind:style="{left: a.player_view.inpoint_float*100 + '%'}" v-if="a.player_view.inpoint_float>0.0"></div>
             <div class="tl-outpoint" v-bind:style="{left: a.player_view.outpoint_float*100 + '%'}"></div>
@@ -352,13 +352,13 @@
               <span class="icon icon-controls-stop"></span>
             </span>
 
-            <span class="tl-title" v-show="a.board.w>=400">{{a.view.filename}}</span>
+            <span class="tl-title" v-show="a.w>=400">{{a.view.filename}}</span>
             <span class="tl-times" class="btn-group">
               <span class="btn btn-md btn-transparent no-p">{{a.player_view.current_time_string}}</span>
-              <span class="btn btn-md btn-transparent no-p" v-show="a.board.w>=170"> / {{a.player_view.total_time_string}}</span>
+              <span class="btn btn-md btn-transparent no-p" v-show="a.w>=170"> / {{a.player_view.total_time_string}}</span>
             </span>
 
-            <span v-show="logged_in && a.board.w>=310">
+            <span v-show="logged_in && a.w>=310">
               <a class="btn btn-xs btn-round btn-icon set-inpoint" title="Set Inpoint at Playhead">
                 <span class="icon icon-edge-left"></span>
               </a>
@@ -464,7 +464,7 @@
 
 <div v-if="active_space_loaded" v-cloak>
   <div id="minimap"
-       v-bind:style="{width: ''+(active_space.advanced.width/minimap_scale)+'px', height: ''+(active_space.advanced.height/minimap_scale)+'px', bottom: '66px', right: '20px'}"
+       v-bind:style="{width: ''+(active_space.width/minimap_scale)+'px', height: ''+(active_space.height/minimap_scale)+'px', bottom: '66px', right: '20px'}"
        v-if="active_space"
         v-on:mousedown="handle_minimap_mousedown($event)"
         v-on:touchstart="handle_minimap_mousedown($event)"
@@ -473,7 +473,7 @@
         v-on:mouseleave="handle_minimap_mouseup($event)"
         v-on:touchend="handle_minimap_mouseup($event)"
         v-on:mouseup="handle_minimap_mouseup($event)">
-    <div v-for="a in active_space_artifacts" v-bind:style="{left: ''+(a.board.x/minimap_scale)+ 'px',  top: ''+(a.board.y/minimap_scale) + 'px', width: ''+(a.board.w/minimap_scale)+ 'px', height: ''+(a.board.h/minimap_scale) + 'px'}"></div>
+    <div v-for="a in active_space_artifacts" v-bind:style="{left: ''+(a.x/minimap_scale)+ 'px',  top: ''+(a.y/minimap_scale) + 'px', width: ''+(a.w/minimap_scale)+ 'px', height: ''+(a.h/minimap_scale) + 'px'}"></div>
     <div class="window" v-bind:style="{left: ''+(scroll_left/minimap_scale) + 'px', top: ''+(scroll_top/minimap_scale)+ 'px', width: ''+(window_width/minimap_scale)+ 'px', height: ''+(window_height/minimap_scale) + 'px'}"></div>
 
   </div>
diff --git a/views/partials/tool/background.html b/views/partials/tool/background.html
index dfe73be0262f8ee56dd3153ffc084af53882d8ac..f7100716f1c7bedce58238dd5ef0b226b00456da 100644
--- a/views/partials/tool/background.html
+++ b/views/partials/tool/background.html
@@ -79,14 +79,14 @@
   </div-->
 
   <div class="" v-show="background_mode=='image'" v-if="active_space">
-    <div class="background-image" v-bind:style="{height: '233px', 'background-image':'url('+active_space.advanced.background_uri+')', 'margin': '6px', 'border-radius': '3px'}" v-if="active_space.advanced.background_uri && !space_background_uploading">
+    <div class="background-image" v-bind:style="{height: '233px', 'background-image':'url('+active_space.background_uri+')', 'margin': '6px', 'border-radius': '3px'}" v-if="active_space.background_uri && !space_background_uploading">
     </div>
         
     <div class="progress state-processing" v-if="space_background_uploading"> 
       <div class="spinner"></div>
     </div>
 
-    <div class="dialog-section no-b adapt" v-if="!active_space.advanced.background_uri && !space_background_uploading" v-on:touchstart="handle_touch_select_background_image()">
+    <div class="dialog-section no-b adapt" v-if="!active_space.background_uri && !space_background_uploading" v-on:touchstart="handle_touch_select_background_image()">
       <label class="btn btn-xxl btn-transparent btn-icon">
         <span class="icon icon-picture-upload"></span>
         <input id="background-uploader" type="file" accept="image/*" v-on:change="handle_section_background_upload($event)">
@@ -94,9 +94,9 @@
       <p>[[__("upload_background_caption")]]</p>
     </div>
 
-    <div class="dialog-section no-p no-flex" v-if="active_space.advanced.background_uri">
+    <div class="dialog-section no-p no-flex" v-if="active_space.background_uri">
       <div class="btn-cluster">
-        <label class="btn btn-transparent btn-block text-center" v-if="active_space.advanced.background_uri" v-on:touchstart="handle_touch_select_background_image()"> 
+        <label class="btn btn-transparent btn-block text-center" v-if="active_space.background_uri" v-on:touchstart="handle_touch_select_background_image()"> 
           <input id="background-uploader" type="file" accept="image/*" v-on:chang="handle_section_background_upload($event)">
           <span class="icon icon-picture-upload"></span>
           <!-- Upload -->
diff --git a/views/partials/tool/pick-mobile.html b/views/partials/tool/pick-mobile.html
index 8ad2c975ad9c35260992b187d5d21bcd99b636e5..27f43d533542b399159ca26603782b9bb349e341 100644
--- a/views/partials/tool/pick-mobile.html
+++ b/views/partials/tool/pick-mobile.html
@@ -1,7 +1,7 @@
 <div id="pick-mobile" v-if="active_space" class="dialog-section" v-show="opened_dialog=='mobile'">
   <h4 class="dialog-title">Mobile Upload</h4>
 
-  <img v-if="active_space.edit_hash" v-bind:src="'/api/helper/qrcode/'+ active_space._id" />
+  <!--img v-if="active_space.edit_hash" v-bind:src="'/api/helper/qrcode/'+ active_space._id"-->
   
   <p class="text-center">
     Install the Spacedeck App on your phone and scan this QR code to upload photos, sound, video or text.
diff --git a/views/partials/tool/toolbar-elements.html b/views/partials/tool/toolbar-elements.html
index c0c0a2faccac418aa65f98a0e8b3d7050cd694d8..c08d8e11235e06acf892a1bc582f5fa83a7b69f6 100644
--- a/views/partials/tool/toolbar-elements.html
+++ b/views/partials/tool/toolbar-elements.html
@@ -76,20 +76,7 @@
         {% include "./zones.html" %}
       </div>
     </div>
-
-    <div id="mobile-dialog" class="dropdown bottom light center static" v-bind:class="{open:opened_dialog=='mobile'}">
-      <div class="btn-collapse in">
-        <button class="btn btn-transparent btn-icon-labeled" v-bind:class="{open:opened_dialog=='mobile'}" v-on:click="open_dialog('mobile')" >
-          <span class="icon icon-device-mobile"></span>
-          <span class="icon-label">[[__("mobile")]]</span>
-        </button>
-      </div>
-
-      <div class="dialog mobile-search">
-        {% include "./pick-mobile.html" %}
-      </div>
-    </div>
-
+    
     <button class="btn btn-divider" v-show="logged_in"></button>
 
     <div class="dropdown bottom light center" v-show="logged_in" v-bind:class="{open:opened_dialog=='background'}">
diff --git a/views/partials/tool/zones.html b/views/partials/tool/zones.html
index 8bfec42a0e8167440259abbb688d822c7776bf98..57f873f3cb20ad96cc11de0c41d58a25984f3c30 100644
--- a/views/partials/tool/zones.html
+++ b/views/partials/tool/zones.html
@@ -9,7 +9,7 @@
     <button v-on:click="add_zone()" class="btn btn-sm btn-primary">[[__("add_zone")]]</button>
   </div>
 
-  <div class="dialog-section no-p" v-for="z in zones | orderBy 'style.order'" style="white-space: nowrap;text-align:left;cursor:pointer" v-on:click="zoom_to_zone(z)">
+  <div class="dialog-section no-p" v-for="z in zones | orderBy 'order'" style="white-space: nowrap;text-align:left;cursor:pointer" v-on:click="zoom_to_zone(z)">
     <button class="btn btn-sm btn-transparent">{{{z.description}}}</button>
     <button v-if="$index==current_zone_idx" v-on:click="sort_zone_up(z)" class="btn btn-sm btn-round btn-transparent btn-icon"><span class="icon icon-triangle-up"></span></button>
     <button v-if="$index==current_zone_idx" v-on:click="sort_zone_down(z)" class="btn btn-sm btn-round btn-transparent btn-icon"><span class="icon icon-triangle-down"></span></button>
diff --git a/views/spacedeck.html b/views/spacedeck.html
index 8cf7ec4eec52b7fcb713631399431e98ef7e2cf0..a9d720a6b81b0b60713b3f2b710cc85a76e787f6 100644
--- a/views/spacedeck.html
+++ b/views/spacedeck.html
@@ -13,6 +13,8 @@
     <link type="text/css" rel="stylesheet" href="https://fast.fonts.net/cssapi/ee1a3484-4d98-4f9f-9f55-020a7b37f3c5.css"/>
     <link rel="stylesheet" href="[[ '/stylesheets/style.css' | cdn ]]">
 
+    <script>if (typeof module === 'object') {window.module = module; module = undefined;}</script>
+    
     <script src="//cdnjs.cloudflare.com/ajax/libs/twemoji/1.3.2/twemoji.min.js"></script>
 
     <script>
@@ -83,6 +85,8 @@
     <script minify src="/javascripts/spacedeck_directives.js"></script>
     <script minify src="/javascripts/spacedeck_vue.js"></script>
     {% endif %}
+    
+    <script>if (window.module) module = window.module;</script>
   </head>
 
   <body id="main" v-bind:class="{'present-mode':present_mode,'modal-open':active_modal}" v-on:click="handle_body_click($event)">