9 Commits

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

3
.gitignore vendored
View File

@@ -1,6 +1,5 @@
node_modules
public/stylesheets/*
javascripts/maps
javascripts/spacedeck.js
*.swp
*~

View File

@@ -1,6 +1,42 @@
var gulp = require('gulp');
var sass = require('gulp-sass');
var concat = require('gulp-concat');
var server = require('gulp-express');
var nodemon = require('gulp-nodemon');
var revReplace = require("gulp-rev-replace");
var clean = require('gulp-clean');
var child_process = require('child_process');
var path = require('path');
var uglify = require('gulp-uglify');
var fingerprint = require('gulp-fingerprint');
var rev = require('gulp-rev');
var revAll = require('gulp-rev-all');
gulp.task('rev', () => {
return gulp.src(['public/**'])
.pipe(gulp.dest('build/assets'))
.pipe(revAll.revision())
.pipe(gulp.dest('build/assets'))
.pipe(revAll.manifestFile())
.pipe(gulp.dest('build/assets'));
});
gulp.task("all", ["styles", "uglify", "rev", "copylocales"], function(){
var manifest = gulp.src("./build/assets/rev-manifest.json");
return gulp.src("./views/**/*")
.pipe(revReplace({manifest: manifest}))
.pipe(gulp.dest("./build/views"));
});
gulp.task('copylocales', function(){
return gulp.src('./locales/*.js').pipe(gulp.dest('./build/locales'));
});
gulp.task('clean', function () {
return gulp.src('./build').pipe(clean());
});
gulp.task('styles', function() {
gulp.src('styles/**/*.scss')
@@ -11,3 +47,15 @@ gulp.task('styles', function() {
.pipe(concat('style.css'));
});
gulp.task('uglify', () => {
child_process.exec('sed -n \'s/.*script minify src="\\(.*\\)".*/.\\/public\\/\\1/p\' views/spacedeck.html',
function (error, stdout, stderr) {
var scripts = stdout.split('\n').map(function(p){return path.normalize(p)}).filter(function(p){return p!='.'});
console.log("scripts: ",scripts);
gulp.src(scripts)
.pipe(uglify({output:{beautify:true}}))
.pipe(concat('spacedeck.js'))
.pipe(gulp.dest('public/javascripts'));
});
});

View File

@@ -4,7 +4,7 @@ This is the free and open source version of Spacedeck, a web based, real time, c
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.
Easy to use desktop releases with binaries for Linux, Mac and Windows will be published here soon. In the meantime, you have to install Node.JS.
Data migration features will be added soon.
We appreciate filed issues, pull requests and general discussion.
@@ -19,28 +19,32 @@ We appreciate filed issues, pull requests and general discussion.
- Share Spaces on the web or via email
- Export your work as printable PDF or ZIP
# Data Import from Spacedeck.com
Spacedeck Open has a data import feature that you can use to migrate your ZIP export from Spacedeck.com.
1. Just copy your downloaded ZIP file into the spacedeck root folder. Don't extract it.
2. Start your local Spacedeck.
3. Navigate to *Account / Profile* (person icon in the top right corner).
4. Click the *Import* button next to the ZIP file name. It is on the bottom of the page.
5. Wait until console output has finished and you're done.
# Requirements, Installation
Spacedeck requires:
Spacedeck uses the following major building blocks:
- Node.js 9.x: Web Server / API. Download: https://nodejs.org
- Node.js 9.x: Web Server / API
- Vue.js: Frontend UI Framework (included)
- SQLite (included)
To run Spacedeck, you only need Node.JS 9.x.
It also has some optional binary dependencies for advanced media conversion:
To install all node dependencies, run (do this once):
- 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.
To use Spacedeck, you only need Node.JS 9.x.
Then, to install all node dependencies, run
npm install
To rebuild the frontend CSS styles (you need to do this at least once):
gulp styles
# Configuration
See [config/default.json](config/default.json)
@@ -55,25 +59,6 @@ Then open http://localhost:9666 in a web browser.
electron .
# Optional Dependencies
For advanced media conversion:
- ffmpeg and ffprobe for video/audio conversion. Download: https://www.ffmpeg.org/download.html
- audiowaveform for audio waveform rendering. Download: https://github.com/bbcrd/audiowaveform
- ghostscript for PDF import. Download: https://www.ghostscript.com/download/gsdnld.html
# Data Storage
By default, media files are uploaded to the ```storage``` folder.
The database is stored in ```database.sqlite``` by default.
# Hacking
To rebuild the frontend CSS styles:
gulp styles
# License
The Spacedeck logo and brand assets are registered trademarks of Spacedeck GmbH. All rights reserved.

View File

@@ -1,17 +0,0 @@
# Windows Electron Build
sqlite3 needs to be manually built for the iojs version that electron ships. The following code assumes electron v1.8.4.
````
npm -g install windows-build-tools
cd node_modules\sqlite3
node-gyp configure --module_name=node_sqlite3 --module_path=../lib/binding/electron-v1.8-win32-x64
node-gyp rebuild --target=1.8.4 --target_platform=win32 --dist-url=https://atom.io/download/atom-shell --module_name=node_sqlite3 --module_path=../lib/binding/electron-v1.8-win32-x64 --msvs_version=2015
cd ..\..
````

View File

@@ -37,7 +37,7 @@ const convertableAudioTypes = [
"application/ogg",
"audio/amr",
"audio/3ga",
"audio/wave",
"audio/wav",
"audio/3gpp",
"audio/x-wav",
"audio/aiff",
@@ -263,8 +263,6 @@ var resizeAndUploadImage = function(a, mimeType, size, fileName, fileNameOrg, im
a.h = Math.round(size.height*factor);
a.updated_at = new Date();
db.packArtifact(a);
a.save().then(function() {
fs.unlink(originalFilePath, function (err) {
if (err){
@@ -330,8 +328,6 @@ module.exports = {
a.h = Math.round(size.height*factor);
a.updated_at = new Date();
db.packArtifact(a);
a.save().then(function() {
fs.unlink(localFilePath, function (err) {
if (err){

View File

@@ -46,7 +46,7 @@
"specify": "Bitte spezifiziere",
"confirm": "Bitte bestätige",
"signup_google": "Mit Google anmelden",
"error_unknown_email": "Unbekannte Kombination von Email und Passwort.",
"error_unknown_email": "Unbekannte Kombination von Email und Passwort. Oder versuche dich mit Google anzumelden.",
"error_password_confirmation": "Die beiden Passwörter stimmen nicht überein.",
"error_domain_blocked": "Diese Domain ist gesperrt.",
"error_user_email_already_used": "Diese Email-Adresse ist bereits registriert.",

View File

@@ -44,7 +44,8 @@
"sure": "Are you sure?",
"specify": "Please Specify",
"confirm": "Please Confirm",
"error_unknown_email": "This email/password combination is unknown.",
"signup_google": "Sign In with Google",
"error_unknown_email": "This email/password combination is unknown. Try login with Google.",
"error_password_confirmation": "The entered passwords don't match.",
"error_domain_blocked": "Your domain is blocked.",
"error_user_email_already_used": "This email address is already in use.",
@@ -321,4 +322,4 @@
"mute_present": "Unfollow",
"follow_present_help": "If someone else is presenting this Space, the other members automatically follow the presentation. Switch following on or off with this button.",
"export": "export"
}
}

View File

@@ -46,7 +46,7 @@
"specify": "Veuillez préciser:",
"confirm": "Veuillez confirmer",
"signup_google": "S'inscrire avec Google",
"error_unknown_email": "Combinaison inconnue de l'email et mot de passe.",
"error_unknown_email": "Combinaison inconnue de l'email et mot de passe. Ou essayer de signer avec Google.",
"error_password_confirmation": "Les deux mots de passe ne correspondent pas.",
"error_domain_blocked": "Ce domaine a été désactivé.",
"error_user_email_already_used": "Cette adresse email est déjà enregistré.",
@@ -315,4 +315,4 @@
"follow_present": "Suivre",
"mute_present": "Pas suivre",
"follow_present_help": "follow_present_help"
}
}

View File

@@ -320,26 +320,26 @@ module.exports = {
},
unpackArtifact: (a) => {
if (a.tags && (typeof a.tags)=="string") {
if (a.tags) {
a.tags = JSON.parse(a.tags);
}
if (a.control_points && (typeof a.control_points)=="string") {
if (a.control_points) {
a.control_points = JSON.parse(a.control_points);
}
if (a.payload_alternatives && (typeof a.payload_alternatives)=="string") {
if (a.payload_alternatives) {
a.payload_alternatives = JSON.parse(a.payload_alternatives);
}
return a;
},
packArtifact: (a) => {
if (a.tags && (typeof a.tags)!="string") {
if (a.tags) {
a.tags = JSON.stringify(a.tags);
}
if (a.control_points && (typeof a.control_points)!="string") {
if (a.control_points) {
a.control_points = JSON.stringify(a.control_points);
}
if (a.payload_alternatives && (typeof a.payload_alternatives)!="string") {
if (a.payload_alternatives) {
a.payload_alternatives = JSON.stringify(a.payload_alternatives);
}
return a;

View File

@@ -26,9 +26,9 @@
"helmet": "^3.5.0",
"i18n-2": "0.6.3",
"log-timestamp": "latest",
"morgan": "1.8.1",
"mock-aws-s3": "^2.6.0",
"moment": "^2.19.3",
"morgan": "1.8.1",
"node-phantom-simple": "2.2.4",
"phantomjs-prebuilt": "2.1.14",
"read-chunk": "^2.1.0",

View File

@@ -41,7 +41,7 @@ var SpacedeckBoardArtifacts = {
if ("medium_for_object" in this) {
var medium = this.medium_for_object[a._id];
if (medium && a._id != this.editing_artifact_id) {
medium.value(a.description.toString());
medium.value(a.description);
}
}
},
@@ -88,11 +88,10 @@ var SpacedeckBoardArtifacts = {
},
artifact_is_text_blank: function(a) {
if (a.description) {
desc = a.description.toString();
var filtered = desc.replace(/<[^>]+>/g,"").replace(/\s/g,"");
if(a.description){
var filtered = a.description.replace(/<[^>]+>/g,"").replace(/\s/g,"");
return (filtered.length<1);
} else {
}else{
return false;
}
},

File diff suppressed because it is too large Load Diff

View File

@@ -26,10 +26,9 @@ router.post('/', function(req, res) {
//res.status(400).json({"error":"session.users"});
})
.then(user => {
if (!user) {
res.sendStatus(404);
}
else if (bcrypt.compareSync(password, user.password_hash)) {
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);

View File

@@ -53,8 +53,15 @@ router.get('/', (req, res) => {
space_id: req.space._id
}}).then(artifacts => {
async.map(artifacts, (a, cb) => {
db.unpackArtifact(a);
//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) {
// FIXME JOIN
/*User.findOne({where: {
@@ -124,8 +131,7 @@ router.post('/:artifact_id/payload', function(req, res, next) {
var stream = req.pipe(writeStream);
var progress_callback = function(progress_msg) {
a.description = progress_msg.toString();
db.packArtifact(a);
a.description = progress_msg;
a.save();
redis.sendMessage("update", a, a.toJSON(), req.channelId);
};

View File

@@ -3,7 +3,6 @@
var config = require('config');
const db = require('../../models/db');
const uuidv4 = require('uuid/v4');
const os = require('os');
var mailer = require('../../helpers/mailer');
var uploader = require('../../helpers/uploader');
@@ -216,8 +215,8 @@ router.post('/:user_id/avatar', (req, res, next) => {
const user = req.user;
const filename = "u"+req.user._id+"_"+(new Date().getTime())+".jpeg"
const localFilePath = os.tmpdir()+"/"+filename;
const localResizedFilePath = os.tmpdir()+"/resized_"+filename;
const localFilePath = "/tmp/"+filename;
const localResizedFilePath = "/tmp/resized_"+filename;
const writeStream = fs.createWriteStream(localFilePath);
const stream = req.pipe(writeStream);

View File

@@ -1,6 +1,7 @@
"use strict";
const config = require('config');
require('../models/db');
const redis = require('../helpers/redis');
const express = require('express');
@@ -9,11 +10,6 @@ const router = express.Router();
const mailer = require('../helpers/mailer');
const _ = require('underscore');
const db = require('../models/db');
const Sequelize = require('sequelize');
const Op = Sequelize.Op;
const uuidv4 = require('uuid/v4');
router.get('/', (req, res) => {
res.render('index', { title: 'Spaces' });
});
@@ -124,30 +120,79 @@ router.get('/t/:id', (req, res) => {
});
router.get('/s/:token', (req, res) => {
var token = req.params.token;
if (token.split("-").length > 0) {
token = token.split("-")[0];
}
redis.rateLimit(req.real_ip, "token", function(ok) {
if (ok) {
var token = req.params.token;
if (token.split("-").length > 0) {
token = token.split("-")[0];
}
db.Space.findOne({where: {"edit_hash": token}}).then(function (space) {
if (space) {
if (req.accepts('text/html')){
res.redirect("/spaces/"+space._id + "?spaceAuth=" + token);
} else {
res.status(200).json(space);
}
Space.findOne({"edit_hash": token}).exec(function (err, space) {
if (err) {
res.status(404).render('not_found', { title: 'Page Not Found.' });
} else {
if (space) {
if(req.accepts('text/html')){
res.redirect("/spaces/"+space._id + "?spaceAuth=" + token);
}else{
res.status(200).json(space);
}
} else {
if(req.accepts('text/html')){
res.status(404).render('not_found', { title: 'Page Not Found.' });
} else {
res.status(404).json({});
}
}
}
});
} else {
if (req.accepts('text/html')) {
res.status(404).render('not_found', { title: 'Page Not Found.' });
} else {
res.status(404).json({});
}
res.status(429).json({"error": "Too Many Requests"});
}
});
});
router.get('/spaces/:id', (req, res) => {
res.render('spacedeck', { title: 'Space' });
if (req.headers['user-agent']) {
if (req.headers['user-agent'].match(/facebook/)) {
Space.findOne({"_id": req.params.id }).exec(function (err, space) {
if (err) {
res.status(400).json(err);
} else {
if (space) {
if (space.access_mode == "public") {
Artifact.find({"space_id": req.params.id }).populate("creator").exec(function(err, artifacts) {
space.artifacts = artifacts;
res.render('facebook', { space: space });
});
} else {
res.redirect("/?error=space_not_accessible");
}
} else {
res.render('not_found', { title: 'Spaces' });
}
}
});
} else {
// not facebook, render javascript
res.render('spacedeck', { title: 'Space' });
}
} else res.render('spacedeck', { title: 'Space' });
});
router.get('/qrcode/:id', function(req, res) {
Space.findOne({"_id": req.params.id}).exec(function(err, space) {
if (space) {
const url = config.get("endpoint") + "/s/"+space.edit_hash;
const code = qr.image(url, { type: 'svg' });
res.type('svg');
code.pipe(res);
} else {
res.status(404).json({
"error": "not_found"
});
}
});
});
module.exports = router;