This commit is contained in:
azivner 2017-10-25 22:39:21 -04:00
parent 5253f680f6
commit 1c733fbfab
10 changed files with 198 additions and 64 deletions

89
bin/www
View file

@ -1,26 +1,28 @@
#!/usr/bin/env node #!/usr/bin/env node
process.on('unhandledRejection', (reason, p) => { process.on('unhandledRejection', (reason, p) => {
const message = 'Unhandled Rejection at: Promise' + p + ', reason:' + reason;
// this makes sure that stacktrace of failed promise is printed out // this makes sure that stacktrace of failed promise is printed out
console.log('Unhandled Rejection at: Promise', p, 'reason:', reason); console.log(message);
// but also try to log it into file
require('../services/log').error(message);
}); });
var app = require('../app'); const app = require('../app');
var debug = require('debug')('node:server'); const debug = require('debug')('node:server');
var http = require('http'); const http = require('http');
/** /**
* Get port from environment and store in Express. * Get port from environment and store in Express.
*/ */
const port = normalizePort(process.env.PORT || '3000');
var port = normalizePort(process.env.PORT || '3000');
app.set('port', port); app.set('port', port);
/** /**
* Create HTTP server. * Create HTTP server.
*/ */
const server = http.createServer(app);
var server = http.createServer(app);
/** /**
* Listen on provided port, on all network interfaces. * Listen on provided port, on all network interfaces.
@ -35,19 +37,19 @@ server.on('listening', onListening);
*/ */
function normalizePort(val) { function normalizePort(val) {
var port = parseInt(val, 10); const port = parseInt(val, 10);
if (isNaN(port)) { if (isNaN(port)) {
// named pipe // named pipe
return val; return val;
} }
if (port >= 0) { if (port >= 0) {
// port number // port number
return port; return port;
} }
return false; return false;
} }
/** /**
@ -55,27 +57,29 @@ function normalizePort(val) {
*/ */
function onError(error) { function onError(error) {
if (error.syscall !== 'listen') { if (error.syscall !== 'listen') {
throw error; throw error;
} }
var bind = typeof port === 'string' const bind = typeof port === 'string'
? 'Pipe ' + port ? 'Pipe ' + port
: 'Port ' + port; : 'Port ' + port;
// handle specific listen errors with friendly messages // handle specific listen errors with friendly messages
switch (error.code) { switch (error.code) {
case 'EACCES': case 'EACCES':
console.error(bind + ' requires elevated privileges'); console.error(bind + ' requires elevated privileges');
process.exit(1); process.exit(1);
break; break;
case 'EADDRINUSE':
console.error(bind + ' is already in use'); case 'EADDRINUSE':
process.exit(1); console.error(bind + ' is already in use');
break; process.exit(1);
default: break;
throw error;
} default:
throw error;
}
} }
/** /**
@ -83,9 +87,10 @@ function onError(error) {
*/ */
function onListening() { function onListening() {
var addr = server.address(); const addr = server.address();
var bind = typeof addr === 'string' const bind = typeof addr === 'string'
? 'pipe ' + addr ? 'pipe ' + addr
: 'port ' + addr.port; : 'port ' + addr.port;
debug('Listening on ' + bind);
debug('Listening on ' + bind);
} }

View file

@ -0,0 +1 @@
INSERT INTO options (opt_name, opt_value) VALUES ('last_synced', 0)

View file

@ -22,6 +22,8 @@
"fs-extra": "^4.0.2", "fs-extra": "^4.0.2",
"helmet": "^3.9.0", "helmet": "^3.9.0",
"ini": "^1.3.4", "ini": "^1.3.4",
"request": "^2.83.0",
"request-promise": "^4.2.2",
"scrypt": "^6.0.3", "scrypt": "^6.0.3",
"serve-favicon": "~2.4.5", "serve-favicon": "~2.4.5",
"session-file-store": "^1.1.2", "session-file-store": "^1.1.2",

View file

@ -10,7 +10,21 @@ router.get('/changed/:since', auth.checkApiAuth, async (req, res, next) => {
res.send({ res.send({
'tree': await sql.getResults("select * from notes_tree where date_modified >= ?", [since]), 'tree': await sql.getResults("select * from notes_tree where date_modified >= ?", [since]),
'notes': await sql.getFlattenedResults('note_id', "select note_id from notes where date_modified >= ?", [since]) 'notes': await sql.getFlattenedResults('note_id', "select note_id from notes where date_modified >= ?", [since]),
'audit_log': await sql.getResults("select * from audit_log where date_modified >= ?", [since])
});
});
router.get('/note/:noteId/:since', auth.checkApiAuth, async (req, res, next) => {
const noteId = req.params.noteId;
const since = parseInt(req.params.since);
const detail = await sql.getSingleResult("select * from notes where note_id = ?", [noteId]);
res.send({
'detail': detail,
'images': await sql.getResults("select * from images where note_id = ? order by note_offset", [noteId]),
'history': await sql.getResults("select * from notes_history where note_id = ? and date_modified_to >= ?", [noteId, since])
}); });
}); });

View file

@ -7,14 +7,7 @@ const migration = require('../services/migration');
const sql = require('../services/sql'); const sql = require('../services/sql');
router.get('', auth.checkAuth, async (req, res, next) => { router.get('', auth.checkAuth, async (req, res, next) => {
const dbVersion = parseInt(await sql.getOption('db_version')) res.render('index', {});
if (dbVersion < migration.APP_DB_VERSION) {
res.redirect("migration");
}
else {
res.render('index', {});
}
}); });
module.exports = router; module.exports = router;

View file

@ -1,19 +1,31 @@
"use strict"; "use strict";
function checkAuth(req, res, next) { const migration = require('./migration');
async function checkAuth(req, res, next) {
if (!req.session.loggedIn) { if (!req.session.loggedIn) {
res.redirect("login"); res.redirect("login");
} else { }
if (await migration.isDbUpToDate()) {
next(); next();
} }
else {
res.redirect("migration");
}
} }
function checkApiAuth(req, res, next) { async function checkApiAuth(req, res, next) {
if (!req.session.loggedIn) { if (!req.session.loggedIn && req.header("auth") !== "sync") {
res.sendStatus(401); res.sendStatus(401);
} else { }
if (await migration.isDbUpToDate()) {
next(); next();
} }
else {
res.sendStatus(409); // need better response than that
}
} }
module.exports = { module.exports = {

View file

@ -3,7 +3,7 @@ const sql = require('./sql');
const fs = require('fs-extra'); const fs = require('fs-extra');
const log = require('./log'); const log = require('./log');
const APP_DB_VERSION = 10; const APP_DB_VERSION = 11;
const MIGRATIONS_DIR = "./migrations"; const MIGRATIONS_DIR = "./migrations";
async function migrate() { async function migrate() {
@ -67,7 +67,14 @@ async function migrate() {
return migrations; return migrations;
} }
async function isDbUpToDate() {
const dbVersion = parseInt(await sql.getOption('db_version'));
return dbVersion >= APP_DB_VERSION;
}
module.exports = { module.exports = {
migrate, migrate,
isDbUpToDate,
APP_DB_VERSION APP_DB_VERSION
}; };

View file

@ -4,11 +4,19 @@ const db = require('sqlite');
const utils = require('./utils'); const utils = require('./utils');
const log = require('./log'); const log = require('./log');
async function insert(table_name, rec) { async function insert(table_name, rec, replace = false) {
const columns = Object.keys(rec).join(", "); const keys = Object.keys(rec);
const questionMarks = Object.keys(rec).map(p => "?").join(", "); if (keys.length === 0) {
log.error("Can't insert empty object into table " + table_name);
return;
}
const res = await execute("INSERT INTO " + table_name + "(" + columns + ") VALUES (" + questionMarks + ")", Object.values(rec)); const columns = keys.join(", ");
const questionMarks = keys.map(p => "?").join(", ");
const query = "INSERT " + (replace ? "OR REPLACE" : "") + " INTO " + table_name + "(" + columns + ") VALUES (" + questionMarks + ")";
const res = await execute(query, Object.values(rec));
return res.lastID; return res.lastID;
} }
@ -21,6 +29,10 @@ async function commit() {
return await db.run("COMMIT"); return await db.run("COMMIT");
} }
async function rollback() {
return await db.run("ROLLBACK");
}
async function getOption(optName) { async function getOption(optName) {
const row = await getSingleResult("SELECT opt_value FROM options WHERE opt_name = ?", [optName]); const row = await getSingleResult("SELECT opt_value FROM options WHERE opt_name = ?", [optName]);
@ -94,6 +106,7 @@ module.exports = {
setOption, setOption,
beginTransaction, beginTransaction,
commit, commit,
rollback,
addAudit, addAudit,
deleteRecentAudits, deleteRecentAudits,
remove remove

View file

@ -1,7 +1,94 @@
"use strict"; "use strict";
function sync() { const log = require('./log');
const rp = require('request-promise');
const sql = require('./sql');
const migration = require('./migration');
const SYNC_SERVER = 'http://localhost:3000';
let syncInProgress = false;
async function sync() {
try {
syncInProgress = true;
if (!await migration.isDbUpToDate()) {
return;
}
const lastSynced = parseInt(await sql.getOption('last_synced'));
const resp = await rp({
uri: SYNC_SERVER + '/api/sync/changed/' + lastSynced,
headers: {
auth: 'sync'
},
json: true
});
try {
sql.beginTransaction();
for (const treeItem of resp.tree) {
delete treeItem['id'];
await sql.insert("notes_tree", treeItem, true);
log.info("Syncing notes_tree " + treeItem.note_id);
}
for (const audit of resp.audit_log) {
delete audit['id'];
await sql.insert("audit_log", audit, true);
log.info("Syncing audit_log for noteId=" + audit.note_id);
}
for (const noteId of resp.notes) {
const note = await rp({
uri: SYNC_SERVER + "/api/sync/note/" + noteId + "/" + lastSynced,
headers: {
auth: 'sync'
},
json: true
});
console.log(noteId);
await sql.insert("notes", note.detail, true);
await sql.remove("images", noteId);
for (const image of note.images) {
await sql.insert("images", image);
}
for (const history of note.history) {
delete history['id'];
await sql.insert("notes_history", history);
}
}
sql.commit();
}
catch (e) {
sql.rollback();
throw e;
}
}
catch (e) {
log.error("sync failed: " + e.stack);
}
finally {
syncInProgress = false;
}
} }
setInterval(sync, 60000); setInterval(sync, 60000);
setTimeout(sync, 1000);

View file

@ -7,7 +7,7 @@ function randomToken(length) {
} }
function newNoteId() { function newNoteId() {
return randomString(32, '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'); return randomString(22, '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ');
} }
function randomString(length, chars) { function randomString(length, chars) {