voterunner

quick and dirty votes and discussions
git clone https://git.ce9e.org/voterunner.git

commit
3ea2fe78e486363d2f7086fda6191d2aaec7cf1c
parent
bd3285c8c3e8563f03ecfb990d369ccac3921b72
Author
Tobias Bengfort <tobias.bengfort@posteo.de>
Date
2020-10-16 14:25
rm server

Diffstat

M .gitignore 2 --
M Makefile 4 ----
D Procfile 1 -
M README.md 22 +---------------------
D app.js 204 ------------------------------------------------------------
D bin/manage_db.sh 33 ---------------------------------
M package.json 9 ---------
R tpl/app.html -> static/index.html 4 +---
M static/src/voterunner.js 2 +-
D tpl/markdown.html 17 -----------------

10 files changed, 3 insertions, 295 deletions


diff --git a/.gitignore b/.gitignore

@@ -1,5 +1,3 @@
    1     1 node_modules
    2    -1 data
    3    -1 db.sqlite3
    4     2 static/style.css
    5     3 static/voterunner.js

diff --git a/Makefile b/Makefile

@@ -8,7 +8,3 @@ static/style.css: static/scss/*.scss
    8     8 
    9     9 clean:
   10    10 	rm static/voterunner.js static/style.css
   11    -1 
   12    -1 .PHONY: server
   13    -1 server: all
   14    -1 	export DATABASE_URL='sqlite3:db.sqlite3' && node app.js

diff --git a/Procfile b/Procfile

@@ -1 +0,0 @@
    1    -1 web: node app.js

diff --git a/README.md b/README.md

@@ -8,8 +8,7 @@ This app tries to allow for quick and dirty votes and discussions. It is
    8     8 basically the core concept behind
    9     9 [votorolla](http://zelea.com/project/votorola/home.xht) mixed with the
   10    10 interface of [etherpad](http://etherpad.org/). Technically, it is a lot
   11    -1 of client side code with a bit of [socket.io](http://socket.io) magic on
   12    -1 the server.
   -1    11 of client side code using a [via](https://github.com/xi/via) server.
   13    12 
   14    13 The voting mechanism
   15    14 --------------------
@@ -60,25 +59,6 @@ the delegation. Now you compete directly with other ideas. Maybe you can
   60    59 convince others, but maybe you should delegate your vote again in order
   61    60 to agree on a compromise.
   62    61 
   63    -1 Install
   64    -1 -------
   65    -1 
   66    -1 Voterunner is a [node.js](http://nodejs.org/) app using
   67    -1 [PostgreSQL](http://www.postgresql.org/) as a database so the following
   68    -1 lines will bring it up:
   69    -1 
   70    -1     $ git clone https://github.com/xi/voterunner
   71    -1     $ cd voterunner
   72    -1     $ npm install
   73    -1     $ bin/manage_db.sh init
   74    -1     $ bin/manage_db.sh start
   75    -1     $ export DATABASE_URL="postgresql://:@localhost/voterunner"
   76    -1     $ node app.js
   77    -1         ... Listening on localhost:5000
   78    -1     $ open http://localhost:5000/  # introduction
   79    -1     $ open http://localhost:5000/my-topic/  # discuss on a topic
   80    -1 
   81    -1 
   82    62 Development
   83    63 -----------
   84    64 

diff --git a/app.js b/app.js

@@ -1,204 +0,0 @@
    1    -1 /*
    2    -1  * voterunner - quick and dirty votes and discussions
    3    -1  *
    4    -1  * copyright: 2013 Tobias Bengfort <tobias.bengfort@gmx.net>
    5    -1  * license: AGPL-3+
    6    -1  * url: http://voterunner.herokuapp.com/
    7    -1  */
    8    -1 
    9    -1 var url = require('url');
   10    -1 var process = require('process');
   11    -1 
   12    -1 var express = require('express');
   13    -1 var http = require('http');
   14    -1 var app = express();
   15    -1 var server = http.Server(app);
   16    -1 var io = require('socket.io').listen(server);
   17    -1 var anyDB = require('any-db');
   18    -1 var fs = require('fs');
   19    -1 var log4js = require('log4js');
   20    -1 var MarkdownIt = require('markdown-it');
   21    -1 
   22    -1 var DATABASE_URL = process.env.DATABASE_URL;
   23    -1 var SQLITE = DATABASE_URL.match(/^sqlite/);
   24    -1 var PORT = process.env.PORT || 5000;
   25    -1 var HOST = process.env.HOST || 'localhost';
   26    -1 
   27    -1 var log = log4js.getLogger();
   28    -1 var md = new MarkdownIt();
   29    -1 
   30    -1 app.use(express.static('static'));
   31    -1 server.listen(PORT, HOST, function() {
   32    -1 	log.info('Listening on ' + HOST + ':' + PORT);
   33    -1 });
   34    -1 
   35    -1 var db = anyDB.createPool(DATABASE_URL, {
   36    -1 	max: SQLITE ? 1 : 10,
   37    -1 });
   38    -1 process.on('exit', (code) => {
   39    -1 	db.close();
   40    -1 });
   41    -1 
   42    -1 var queryDB = function(sql, data) {
   43    -1 	if (SQLITE) {
   44    -1 		sql = sql.replace(/\$/g, '?');
   45    -1 	}
   46    -1 
   47    -1 	return new Promise(function(resolve, reject) {
   48    -1 		var q = db.query(sql, data, function(err, res) {
   49    -1 			if (err) {
   50    -1 				reject(err);
   51    -1 			} else {
   52    -1 				resolve(res);
   53    -1 			}
   54    -1 		});
   55    -1 	});
   56    -1 };
   57    -1 
   58    -1 // setup table
   59    -1 queryDB('CREATE TABLE IF NOT EXISTS nodes (topic TEXT, id TEXT, name TEXT, comment TEXT, delegate TEXT, UNIQUE (topic, id))');
   60    -1 
   61    -1 var escapeHTML = function(unsafe) {
   62    -1 	return unsafe
   63    -1 		.replace(/&/g, '&amp;')
   64    -1 		.replace(/</g, '&lt;')
   65    -1 		.replace(/>/g, '&gt;')
   66    -1 		.replace(/"/g, '&quot;')
   67    -1 		.replace(/'/g, '&#039;');
   68    -1 };
   69    -1 
   70    -1 var tpl = function(file, data, res) {
   71    -1 	fs.readFile('tpl/' + file, 'utf8', function(err, html) {
   72    -1 		html = html.replace(/{{{ ([^}]*)\|markdown }}}/g, function(match, key) {
   73    -1 			if (data.hasOwnProperty(key)) {
   74    -1 				return md.render(data[key]);
   75    -1 			} else {
   76    -1 				return '';
   77    -1 			}
   78    -1 		});
   79    -1 
   80    -1 		html = html.replace(/{{ ([^}]*)\|json }}/g, function(match, key) {
   81    -1 			if (data.hasOwnProperty(key)) {
   82    -1 				return '<div id="json-' + escapeHTML(key) + '" data-value="' + escapeHTML(JSON.stringify(data[key])) + '"></div>';
   83    -1 			} else {
   84    -1 				return '';
   85    -1 			}
   86    -1 		});
   87    -1 
   88    -1 		html = html.replace(/{{ ([^}]*) }}/g, function(match, key) {
   89    -1 			if (data.hasOwnProperty(key)) {
   90    -1 				return escapeHTML(data[key].toString());
   91    -1 			} else {
   92    -1 				return '';
   93    -1 			}
   94    -1 		});
   95    -1 
   96    -1 		res.send(html);
   97    -1 	});
   98    -1 };
   99    -1 
  100    -1 
  101    -1 // welcome view
  102    -1 app.get('/', function(req, res) {
  103    -1 	fs.readFile('README.md', 'utf8', function(err, markdown) {
  104    -1 		tpl('markdown.html', {'markdown': markdown}, res);
  105    -1 	});
  106    -1 });
  107    -1 
  108    -1 // json state
  109    -1 app.get('/:topic.json', function(req, res) {
  110    -1 	var topic = req.params.topic;
  111    -1 	var sql = 'SELECT id, name, comment, delegate FROM nodes WHERE topic = $1';
  112    -1 
  113    -1 	queryDB(sql, [topic]).then(function(result) {
  114    -1 		res.json(result.rows);
  115    -1 	}).catch(function(err) {
  116    -1 		res.status(500).send(err.toString());
  117    -1 	});
  118    -1 });
  119    -1 
  120    -1 // app view
  121    -1 app.get('/:topic/:id?', function(req, res) {
  122    -1 	var topic = req.params.topic;
  123    -1 
  124    -1 	var sql = 'SELECT id, name, comment, delegate FROM nodes WHERE topic = $1';
  125    -1 	queryDB(sql, [topic]).then(function(result) {
  126    -1 		tpl('app.html', {'nodes': result.rows, 'topic': topic}, res);
  127    -1 	}).catch(function(err) {
  128    -1 		res.status(500).send(err.toString());
  129    -1 	});
  130    -1 });
  131    -1 
  132    -1 // socket.io
  133    -1 io.sockets.on('connection', function(socket) {
  134    -1 	var topic;
  135    -1 	var id;
  136    -1 
  137    -1 	var handleMsg = function(action, sql, v1, v2) {
  138    -1 		// make sure that node exists, ignore error
  139    -1 		return queryDB('INSERT INTO nodes (topic, id) VALUES ($1, $2)', [topic, id]).catch(function() {
  140    -1 			return;
  141    -1 		}).then(function() {
  142    -1 			log.debug('Handeling:', action, topic, id, v1, v2);
  143    -1 			io.to(topic).emit(action, id, v1, v2);
  144    -1 
  145    -1 			if (typeof(sql) === 'string') {
  146    -1 				sql = [sql];
  147    -1 			}
  148    -1 
  149    -1 			return Promise.all(sql.map(function(s) {
  150    -1 				var params = [topic, id];
  151    -1 				var n = s.match(/\$/g).length;
  152    -1 				if (n >= 3) params.push(v1);
  153    -1 				if (n >= 4) params.push(v2);
  154    -1 
  155    -1 				return queryDB(s, params);
  156    -1 			}));
  157    -1 		});
  158    -1 	};
  159    -1 
  160    -1 	socket.on('register', function(_topic, _id) {
  161    -1 		log.debug('Registration:', _topic, _id);
  162    -1 
  163    -1 		topic = _topic;
  164    -1 		id = _id;
  165    -1 		socket.join(topic, function(err) {
  166    -1 			if (err) {
  167    -1 				log.error(err);
  168    -1 			}
  169    -1 		});
  170    -1 	});
  171    -1 
  172    -1 	socket.on('rmNode', function() {
  173    -1 		var sql = [
  174    -1 			'UPDATE nodes SET delegate = null WHERE topic = $1 AND delegate = $2',
  175    -1 			'DELETE FROM nodes WHERE topic = $1 AND id = $2',
  176    -1 		];
  177    -1 		handleMsg('rmNode', sql);
  178    -1 	});
  179    -1 	socket.on('setNodeName', function(name) {
  180    -1 		var sql = 'UPDATE nodes SET name = $3 WHERE topic = $1 AND id = $2';
  181    -1 		handleMsg('setNodeName', sql, name);
  182    -1 	});
  183    -1 	socket.on('setNodeComment', function(comment) {
  184    -1 		var sql = 'UPDATE nodes SET comment = $3 WHERE topic = $1 AND id = $2';
  185    -1 		handleMsg('setNodeComment', sql, comment);
  186    -1 	});
  187    -1 	socket.on('setDelegate', function(delegate) {
  188    -1 		var sql = 'UPDATE nodes SET delegate = $3 WHERE topic = $1 AND id = $2';
  189    -1 		handleMsg('setDelegate', sql, delegate);
  190    -1 	});
  191    -1 	socket.on('rmDelegate', function() {
  192    -1 		var sql = 'UPDATE nodes SET delegate = null WHERE topic = $1 AND id = $2';
  193    -1 		handleMsg('rmDelegate', sql);
  194    -1 	});
  195    -1 
  196    -1 	socket.on('testClear', function(done) {
  197    -1 		if (topic.substr(0, 4) === 'test') {
  198    -1 			log.debug('Handeling:', 'testClear', topic);
  199    -1 			queryDB("DELETE FROM nodes WHERE topic = $1", [topic]).then(done);
  200    -1 		} else {
  201    -1 			done();
  202    -1 		}
  203    -1 	});
  204    -1 });

diff --git a/bin/manage_db.sh b/bin/manage_db.sh

@@ -1,33 +0,0 @@
    1    -1 #!/bin/sh
    2    -1 
    3    -1 cd "$(dirname "$0")/.."
    4    -1 DB_DIR="$(pwd)/data/postgres"
    5    -1 mkdir -p $DB_DIR
    6    -1 
    7    -1 start() {
    8    -1 	pg_ctl start -w -D "$DB_DIR" -o "-h localhost" -o "-k '$DB_DIR'"
    9    -1 }
   10    -1 
   11    -1 stop() {
   12    -1 	pg_ctl stop -D "$DB_DIR"
   13    -1 }
   14    -1 
   15    -1 if [ "$1" = 'start' ]; then
   16    -1 	start
   17    -1 elif [ "$1" = 'stop' ]; then
   18    -1 	stop
   19    -1 elif [ "$1" = 'init' ]; then
   20    -1 	if test ! -d "$DB_DIR/base"; then
   21    -1 		pg_ctl initdb -D "$DB_DIR"
   22    -1 		start
   23    -1 		createdb -h "$DB_DIR" voterunner
   24    -1 		stop
   25    -1 	else
   26    -1 		echo "skipping"
   27    -1 	fi
   28    -1 elif [ "$1" = 'clean' ]; then
   29    -1 	rm -r "$DB_DIR"
   30    -1 else
   31    -1 	echo "invalid command"
   32    -1 	exit 1
   33    -1 fi

diff --git a/package.json b/package.json

@@ -1,15 +1,6 @@
    1     1 {
    2     2 	"name": "voterunner",
    3     3 	"version": "0.0.1",
    4    -1 	"dependencies": {
    5    -1 		"any-db": "^2.2.0",
    6    -1 		"any-db-postgres": "^2.1.5",
    7    -1 		"any-db-sqlite3": "^2.1.4",
    8    -1 		"express": "^4.16.2",
    9    -1 		"log4js": "^0.6.38",
   10    -1 		"markdown-it": "^7.0.1",
   11    -1 		"socket.io": "^1.7.4"
   12    -1 	},
   13     4 	"devDependencies": {
   14     5 		"mfbs": "^3.1.1",
   15     6 		"preact": "^8.2.6",

diff --git a/tpl/app.html b/static/index.html

@@ -2,7 +2,7 @@
    2     2 <html class="voterunner">
    3     3 <head>
    4     4 	<meta charset="utf-8">
    5    -1 	<title>voterunner - {{ topic }}</title>
   -1     5 	<title>voterunner</title>
    6     6 	<meta name="viewport" content="width=device-width" />
    7     7 	<meta name="robots" content="noindex" />
    8     8 	<link rel="shortcut icon" href="/favicon.ico"/>
@@ -36,8 +36,6 @@
   36    36 		<div id="tree"></div>
   37    37 	</div></div>
   38    38 
   39    -1 	{{ nodes|json }}
   40    -1 
   41    39 	<script type="text/javascript" src="/voterunner.js"></script>
   42    40 </body>
   43    41 </html>

diff --git a/static/src/voterunner.js b/static/src/voterunner.js

@@ -14,7 +14,7 @@ document.addEventListener('DOMContentLoaded', function() {
   14    14 	socket.emit('register', TOPIC, ID);
   15    15 
   16    16 	var state = {
   17    -1 		nodes: JSON.parse(document.querySelector('#json-nodes').dataset.value),
   -1    17 		nodes: [],
   18    18 		id: ID,
   19    19 	};
   20    20 

diff --git a/tpl/markdown.html b/tpl/markdown.html

@@ -1,17 +0,0 @@
    1    -1 <!DOCTYPE html>
    2    -1 <html xmlns="http://www.w3.org/1999/xhtml">
    3    -1 
    4    -1 <head>
    5    -1 	<meta charset="utf-8">
    6    -1 	<title>voterunner</title>
    7    -1 	<meta name="viewport" content="width=device-width" />
    8    -1 	<link rel="shortcut icon" href="/favicon.ico"/>
    9    -1 	<link rel="stylesheet" type="text/css" href="/style.css" />
   10    -1 	<meta name="description" content="quick and dirty votes and discussions" />
   11    -1 </head>
   12    -1 
   13    -1 <body>
   14    -1 	{{{ markdown|markdown }}}
   15    -1 </body>
   16    -1 
   17    -1 </html>