Move routing to separate files
This commit is contained in:
parent
5192d7e22d
commit
646fe6ac14
|
@ -0,0 +1,12 @@
|
||||||
|
module.exports = function (app) {
|
||||||
|
app.server.get('/about', (req, res) => {
|
||||||
|
const resourcePath = (req.url.substr(-1) === '/' ? '../' : './');
|
||||||
|
const body = app.fillTemplate('./templates/pages/about.html', { resourcePath });
|
||||||
|
const html = app.fillTemplate('./templates/htmlContainer.html', { title: 'About', body });
|
||||||
|
if (html) {
|
||||||
|
res.send(html);
|
||||||
|
} else {
|
||||||
|
res.send('Something went wrong!');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
module.exports = function (app) {
|
||||||
|
app.server.get('/give', (req, res) => {
|
||||||
|
const resourcePath = (req.url.substr(-1) === '/' ? '../' : './');
|
||||||
|
let body = app.fillTemplate('./templates/pages/uploadForm.html', { resourcePath });
|
||||||
|
body = app.replaceBodyWithTooManyBooksWarning(body);
|
||||||
|
|
||||||
|
const html = app.fillTemplate('./templates/htmlContainer.html', { title: 'Give a Book', resourcePath, body });
|
||||||
|
res.send(html);
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
const path = require('path');
|
||||||
|
const fs = require('fs');
|
||||||
|
const snarkdown = require('snarkdown');
|
||||||
|
const fecha = require('fecha');
|
||||||
|
|
||||||
|
module.exports = function (app) {
|
||||||
|
app.server.get('/history', (req, res) => {
|
||||||
|
const files = fs.readdirSync(app.historyLocation).filter(fileName => fileName.includes('.json'))
|
||||||
|
.map(fileName => { // Cache the file data so sorting doesn't need to re-check each file
|
||||||
|
return { name: fileName, time: fs.statSync(path.resolve(app.historyLocation, fileName)).mtime.getTime() };
|
||||||
|
}).sort((a, b) => b.time - a.time).map(v => v.name); // Sort from newest to oldest.
|
||||||
|
|
||||||
|
let history = files.map(fileName => {
|
||||||
|
const bookData = JSON.parse(fs.readFileSync(path.resolve(app.historyLocation, fileName), 'utf8'));
|
||||||
|
bookData.author = bookData.author ? bookData.author : '<em>author not provided</em>';
|
||||||
|
bookData.contributor = bookData.contributor ? bookData.contributor : 'Anonymous';
|
||||||
|
const id = fileName.replace('.json', '');
|
||||||
|
const added = fecha.format(new Date(bookData.added), 'hh:mm:ssA on dddd MMMM Do, YYYY');
|
||||||
|
const removed = fecha.format(new Date(parseInt(id)), 'hh:mm:ssA on dddd MMMM Do, YYYY');
|
||||||
|
const removedTag = '<div class="control"><div class="tags has-addons"><span class="tag">Taken</span><span class="tag is-warning">' + removed + '</span></div></div>';
|
||||||
|
const modal = app.fillTemplate('./templates/elements/modalCard.html', {
|
||||||
|
id,
|
||||||
|
header: '<h2 class="title">' + bookData.title + '</h2><h4 class="subtitle">' + bookData.author + '</h4>',
|
||||||
|
content: app.fillTemplate('./templates/elements/bookInfo.html', {
|
||||||
|
contributor: bookData.contributor,
|
||||||
|
fileFormat: bookData.fileType,
|
||||||
|
added,
|
||||||
|
removedTag,
|
||||||
|
summary: snarkdown(bookData.summary),
|
||||||
|
}),
|
||||||
|
footer: '<a class="button close">Close</a>',
|
||||||
|
});
|
||||||
|
return app.fillTemplate('./templates/elements/book.html', {
|
||||||
|
id,
|
||||||
|
title: bookData.title,
|
||||||
|
author: bookData.author,
|
||||||
|
fileType: bookData.fileType,
|
||||||
|
modal,
|
||||||
|
});
|
||||||
|
}).join('');
|
||||||
|
|
||||||
|
if (history == '') {
|
||||||
|
history = '<div class="column"><div class="content">No books have been taken yet. Would you like to <a href="/">take a book</a>?</div></div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
const body = '<h2 class="title">History</h2><div class="columns is-multiline">' + history + '</div>';
|
||||||
|
const html = app.fillTemplate('./templates/htmlContainer.html', {
|
||||||
|
title: 'History',
|
||||||
|
resourcePath: (req.url.substr(-1) === '/' ? '../' : './'),
|
||||||
|
body
|
||||||
|
});
|
||||||
|
|
||||||
|
if (html) {
|
||||||
|
res.send(html);
|
||||||
|
} else {
|
||||||
|
res.send('Something went wrong!');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,66 @@
|
||||||
|
const path = require('path');
|
||||||
|
const fs = require('fs');
|
||||||
|
const snarkdown = require('snarkdown');
|
||||||
|
const fecha = require('fecha');
|
||||||
|
|
||||||
|
module.exports = function (app) {
|
||||||
|
app.server.get('/', (req, res) => {
|
||||||
|
const files = fs.readdirSync(app.fileLocation).filter(fileName => fileName.includes('.json'))
|
||||||
|
.map(fileName => { // Cache the file data so sorting doesn't need to re-check each file
|
||||||
|
return { name: fileName, time: fs.statSync(path.resolve(app.fileLocation, fileName)).mtime.getTime() };
|
||||||
|
}).sort((a, b) => a.time - b.time).map(v => v.name); // Sort from oldest to newest.
|
||||||
|
|
||||||
|
let books = files.map(fileName => {
|
||||||
|
const bookData = JSON.parse(fs.readFileSync(path.resolve(app.fileLocation, fileName), 'utf8'));
|
||||||
|
if (bookData.hasOwnProperty('fileName')) return '';
|
||||||
|
bookData.author = bookData.author ? bookData.author : '<em>author not provided</em>';
|
||||||
|
bookData.contributor = bookData.contributor ? bookData.contributor : 'Anonymous';
|
||||||
|
|
||||||
|
const id = fileName.replace('.json', '');
|
||||||
|
const confirmId = 'confirm_' + id;
|
||||||
|
const added = fecha.format(new Date(bookData.added), 'hh:mm:ssA on dddd MMMM Do, YYYY');
|
||||||
|
const modal = app.fillTemplate('./templates/elements/modalCard.html', {
|
||||||
|
id,
|
||||||
|
header: '<h2 class="title">' + bookData.title + '</h2><h4 class="subtitle">' + bookData.author + '</h4>',
|
||||||
|
content: app.fillTemplate('./templates/elements/bookInfo.html', {
|
||||||
|
contributor: bookData.contributor,
|
||||||
|
fileFormat: bookData.fileType,
|
||||||
|
added,
|
||||||
|
summary: snarkdown(bookData.summary),
|
||||||
|
})
|
||||||
|
+ app.fillTemplate('./templates/elements/modal.html', {
|
||||||
|
id: confirmId,
|
||||||
|
content: app.fillTemplate('./templates/elements/messageBox.html', {
|
||||||
|
header: 'Download Your Book',
|
||||||
|
message: app.fillTemplate('./templates/elements/takeConfirm.html', { id }),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
footer: '<a class="button close">Close</a> <a class="button is-success modal-button" data-modal="' + confirmId + '">Take Book</a>',
|
||||||
|
});
|
||||||
|
return app.fillTemplate('./templates/elements/book.html', {
|
||||||
|
id,
|
||||||
|
title: bookData.title,
|
||||||
|
author: bookData.author,
|
||||||
|
fileType: bookData.fileType,
|
||||||
|
modal,
|
||||||
|
});
|
||||||
|
}).join('');
|
||||||
|
|
||||||
|
if (books == '') {
|
||||||
|
books = '<div class="column"><div class="content">The shelf is empty. Would you like to <a href="/give">add a book</a>?</div></div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
const body = '<h2 class="title">Available Books</h2><div class="columns is-multiline">' + books + '</div>';
|
||||||
|
const html = app.fillTemplate('./templates/htmlContainer.html', {
|
||||||
|
title: 'View',
|
||||||
|
resourcePath: (req.url.substr(-1) === '/' ? '../' : './'),
|
||||||
|
body
|
||||||
|
});
|
||||||
|
|
||||||
|
if (html) {
|
||||||
|
res.send(html);
|
||||||
|
} else {
|
||||||
|
res.send('Something went wrong!');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
const path = require('path');
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
module.exports = function (app) {
|
||||||
|
app.server.get('/tools', (req, res) => {
|
||||||
|
if (req.query.pass === settings.toolsPassword) {
|
||||||
|
const templateValues = {};
|
||||||
|
let html = app.fillTemplate('./templates/pages/tools.html', templateValues);
|
||||||
|
|
||||||
|
if (req.query.do && ['resetVisitors'].includes(req.query.do)) {
|
||||||
|
app.connections = 0;
|
||||||
|
templateValues.resetVisitors = 'Done!';
|
||||||
|
html = app.fillTemplate('./templates/pages/tools.html', templateValues);
|
||||||
|
res.send(html);
|
||||||
|
} else if (req.query.dl && ['files', 'history'].includes(req.query.dl)) {
|
||||||
|
const onezip = require('onezip');
|
||||||
|
const { dl } = req.query;
|
||||||
|
const saveLocation = path.resolve(app.fileLocation, dl + 'Backup.zip');
|
||||||
|
const backupLocation = dl === 'history' ? app.historyLocation : app.fileLocation;
|
||||||
|
const files = fs.readdirSync(backupLocation).filter(fileName => !fileName.includes('.zip'));
|
||||||
|
onezip.pack(backupLocation, saveLocation, files)
|
||||||
|
.on('start', () => {
|
||||||
|
console.info('Starting a backup zip of ' + dl)
|
||||||
|
})
|
||||||
|
.on('error', (error) => {
|
||||||
|
console.error(error);
|
||||||
|
templateValues[dl + 'Download'] = 'Something went wrong: ' + JSON.stringify(error);
|
||||||
|
html = app.fillTemplate('./templates/pages/tools.html', templateValues);
|
||||||
|
res.send(html);
|
||||||
|
})
|
||||||
|
.on('end', () => {
|
||||||
|
console.log('Backup complete. Saved to ' + saveLocation);
|
||||||
|
let backupLocation = saveLocation.replace(/\\/g, '/');
|
||||||
|
backupLocation = backupLocation.substr(backupLocation.lastIndexOf('/'));
|
||||||
|
templateValues[dl + 'Download'] = '<a download href="' + encodeURI('./files' + backupLocation) + '">Download</a> (This will be removed from the server in 1 hour)';
|
||||||
|
html = app.fillTemplate('./templates/pages/tools.html', templateValues);
|
||||||
|
res.send(html);
|
||||||
|
console.log('Will delete ' + saveLocation + ' in 1 hour');
|
||||||
|
setTimeout(() => {
|
||||||
|
fs.unlink(saveLocation, (err) => {
|
||||||
|
if (err) {
|
||||||
|
console.error(err);
|
||||||
|
} else {
|
||||||
|
console.log('Deleted backup file ' + saveLocation);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}, 60 * 60 * 1000);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
res.send(html);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
res.status(400).send();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
const path = require('path');
|
||||||
|
const fs = require('fs');
|
||||||
|
const express = require('express');
|
||||||
|
const helmet = require('helmet');
|
||||||
|
const bodyParser = require('body-parser');
|
||||||
|
const fileUpload = require('express-fileupload');
|
||||||
|
|
||||||
|
const settings = require('../settings.json');
|
||||||
|
|
||||||
|
module.exports = function (app) {
|
||||||
|
app.server.use(helmet());
|
||||||
|
|
||||||
|
app.server.use(bodyParser.json()); // support json encoded bodies
|
||||||
|
app.server.use(bodyParser.urlencoded({ extended: true })); // support encoded bodies
|
||||||
|
|
||||||
|
app.server.use('/give', fileUpload({ // support file uploads
|
||||||
|
limits: {
|
||||||
|
fileSize: (settings.maxFileSize > 0 ? settings.maxFileSize * 1024 * 1024 : Infinity), // filesize in bytes (settings accepts MB)
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
app.server.use('/tools', fileUpload()); // Allow file upload on backup with no limits.
|
||||||
|
|
||||||
|
app.server.use('/files', express.static(path.resolve('./public/files/')));
|
||||||
|
app.server.use('/css', express.static(path.resolve('./node_modules/bulma/css/')));
|
||||||
|
app.server.use('/css', express.static(path.resolve('./public/css/')));
|
||||||
|
app.server.use('/js', express.static(path.resolve('./public/js/')));
|
||||||
|
app.server.use('/js', express.static(path.resolve('./node_modules/jquery/dist/')));
|
||||||
|
app.server.use('/js', express.static(path.resolve('./node_modules/socket.io-client/dist/')));
|
||||||
|
|
||||||
|
// If a `.well-known` directory exists, allow it to be used for things like Let's Encrypt challenges
|
||||||
|
if (fs.existsSync(path.resolve('./.well-known'))) {
|
||||||
|
app.server.use('/.well-known', express.static(path.resolve('./.well-known')));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (app.https && settings.forceHTTPS) {
|
||||||
|
app.server.use(function (req, res, next) {
|
||||||
|
if (!req.secure) {
|
||||||
|
return res.redirect(['https://', req.get('Host'), req.baseUrl].join(''));
|
||||||
|
}
|
||||||
|
next(); 0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
module.exports = function (app) {
|
||||||
|
app.server.post('/give', (req, res) => {
|
||||||
|
const resourcePath = (req.url.substr(-1) === '/' ? '../' : './');
|
||||||
|
const { title, author, summary, contributor } = req.body;
|
||||||
|
if (Object.keys(req.files).length > 0
|
||||||
|
&& req.body.hasOwnProperty('title') && title.trim() !== ''
|
||||||
|
&& req.body.hasOwnProperty('summary') && summary.trim() !== '') {
|
||||||
|
const { book } = req.files;
|
||||||
|
const fileType = book.name.substr(book.name.lastIndexOf('.'));
|
||||||
|
app.addBook({ book, title, author, summary, contributor, fileType }, () => {
|
||||||
|
const messageBox = app.fillTemplate('./templates/elements/messageBox.html', {
|
||||||
|
style: 'is-success',
|
||||||
|
header: 'Upload Successful',
|
||||||
|
message: 'Thank you for your contribution!'
|
||||||
|
});
|
||||||
|
const modal = app.fillTemplate('./templates/elements/modal.html', {
|
||||||
|
isActive: 'is-active',
|
||||||
|
content: messageBox,
|
||||||
|
});
|
||||||
|
let body = app.fillTemplate('./templates/pages/uploadForm.html', { resourcePath });
|
||||||
|
body = app.replaceBodyWithTooManyBooksWarning(body);
|
||||||
|
const html = app.fillTemplate('./templates/htmlContainer.html', { title: 'Give a Book', resourcePath, body, modal });
|
||||||
|
res.send(html);
|
||||||
|
}, (err) => {
|
||||||
|
const messageBox = app.fillTemplate('./templates/elements/messageBox.html', {
|
||||||
|
style: 'is-danger',
|
||||||
|
header: 'Upload Failed',
|
||||||
|
message: err,
|
||||||
|
});
|
||||||
|
const modal = app.fillTemplate('./templates/elements/modal.html', {
|
||||||
|
isActive: 'is-active',
|
||||||
|
content: messageBox,
|
||||||
|
});
|
||||||
|
let body = app.fillTemplate('./templates/pages/uploadForm.html', { resourcePath, title, author, summary, contributor });
|
||||||
|
body = app.replaceBodyWithTooManyBooksWarning(body);
|
||||||
|
const html = app.fillTemplate('./templates/htmlContainer.html', { title: 'Give a Book', resourcePath, body, modal });
|
||||||
|
res.send(html);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
let errorMessage = '';
|
||||||
|
if (Object.keys(req.files).length <= 0) {
|
||||||
|
errorMessage += 'You have not selected a file.';
|
||||||
|
}
|
||||||
|
if (!req.body.hasOwnProperty('title') || req.body.title.trim() === '') {
|
||||||
|
errorMessage += (errorMessage.length > 0 ? '<br>' : '') + 'You have not written a title.';
|
||||||
|
}
|
||||||
|
if (!req.body.hasOwnProperty('summary') || req.body.summary.trim() === '') {
|
||||||
|
errorMessage += (errorMessage.length > 0 ? '<br>' : '') + 'You have not written a summary.';
|
||||||
|
}
|
||||||
|
const message = app.fillTemplate('./templates/elements/messageBox.html', {
|
||||||
|
style: 'is-danger',
|
||||||
|
header: 'Missing Required Fields',
|
||||||
|
message: errorMessage,
|
||||||
|
});
|
||||||
|
let body = app.fillTemplate('./templates/pages/uploadForm.html', { resourcePath, title, author, summary, contributor });
|
||||||
|
body = app.replaceBodyWithTooManyBooksWarning(body);
|
||||||
|
const html = app.fillTemplate('./templates/htmlContainer.html', { title: 'Give a Book', resourcePath, body, message });
|
||||||
|
res.send(html);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
const path = require('path');
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
module.exports = function (app) {
|
||||||
|
app.server.post('/tools', (req, res) => {
|
||||||
|
if (req.query.pass === settings.toolsPassword) {
|
||||||
|
const templateValues = {};
|
||||||
|
let html = app.fillTemplate('./templates/pages/tools.html', templateValues);
|
||||||
|
|
||||||
|
const { files } = req;
|
||||||
|
if (Object.keys(files).length > 0) {
|
||||||
|
const backupType = Object.keys(files)[0];
|
||||||
|
if (['files', 'history'].includes(backupType)) {
|
||||||
|
const onezip = require('onezip');
|
||||||
|
const uploadPath = path.resolve('./', backupType + 'UploadedBackup.zip');
|
||||||
|
files[backupType].mv(uploadPath, (err) => {
|
||||||
|
if (err) {
|
||||||
|
console.error(error);
|
||||||
|
templateValues[backupType + 'UploadSuccess'] = 'Could not upload the file.';
|
||||||
|
html = app.fillTemplate('./templates/pages/tools.html', templateValues);
|
||||||
|
res.send(html);
|
||||||
|
} else {
|
||||||
|
onezip.extract(uploadPath, path.resolve('./public', backupType))
|
||||||
|
.on('start', () => {
|
||||||
|
console.info('Extracting file ' + uploadPath)
|
||||||
|
})
|
||||||
|
.on('error', (error) => {
|
||||||
|
console.error(error);
|
||||||
|
templateValues[backupType + 'UploadSuccess'] = 'Something went wrong: ' + JSON.stringify(error);
|
||||||
|
html = app.fillTemplate('./templates/pages/tools.html', templateValues);
|
||||||
|
res.send(html);
|
||||||
|
})
|
||||||
|
.on('end', () => {
|
||||||
|
templateValues[backupType + 'UploadSuccess'] = 'Uploaded Successfully!';
|
||||||
|
html = app.fillTemplate('./templates/pages/tools.html', templateValues);
|
||||||
|
res.send(html);
|
||||||
|
fs.unlink(uploadPath, (err) => {
|
||||||
|
if (err) {
|
||||||
|
console.error(err);
|
||||||
|
} else {
|
||||||
|
console.log('Deleted backup file ' + uploadPath);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
templateValues['generalError'] = '<p>' + backupType + ' is not a valid backup type.</p>';
|
||||||
|
html = app.fillTemplate('./templates/pages/tools.html', templateValues);
|
||||||
|
res.send(html);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
res.send(html);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
res.status(400).send();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
const settings = require('../settings.json');
|
||||||
|
|
||||||
|
module.exports = function (app) {
|
||||||
|
app.io.on('connection', socket => {
|
||||||
|
if (!settings.hideVisitors) {
|
||||||
|
app.connections++;
|
||||||
|
app.io.emit('update visitors', app.connections);
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.on('take book', bookId => {
|
||||||
|
const fileLocation = app.takeBook(bookId, socket.id);
|
||||||
|
if (fileLocation) {
|
||||||
|
console.log(socket.id + ' removed ' + bookId);
|
||||||
|
const downloadLocation = fileLocation.substr(fileLocation.lastIndexOf('/'));
|
||||||
|
socket.emit('get book', encodeURI('./files' + downloadLocation));
|
||||||
|
socket.broadcast.emit('remove book', bookId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('disconnect', () => {
|
||||||
|
if (!settings.hideVisitors) {
|
||||||
|
app.connections--;
|
||||||
|
if (app.connections < 0) app.connections = 0;
|
||||||
|
app.io.emit('update visitors', app.connections);
|
||||||
|
}
|
||||||
|
app.deleteBooks(socket.id);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
394
server.js
394
server.js
|
@ -4,14 +4,9 @@ const express = require('express');
|
||||||
const http = require('http');
|
const http = require('http');
|
||||||
const https = require('https');
|
const https = require('https');
|
||||||
const SocketIoServer = require('socket.io');
|
const SocketIoServer = require('socket.io');
|
||||||
const helmet = require('helmet');
|
|
||||||
const bodyParser = require('body-parser');
|
|
||||||
const fileUpload = require('express-fileupload');
|
|
||||||
const filenamify = require('filenamify');
|
const filenamify = require('filenamify');
|
||||||
const unusedFilename = require('unused-filename');
|
const unusedFilename = require('unused-filename');
|
||||||
const striptags = require('striptags');
|
const striptags = require('striptags');
|
||||||
const snarkdown = require('snarkdown');
|
|
||||||
const fecha = require('fecha');
|
|
||||||
|
|
||||||
const settings = require('./settings.json');
|
const settings = require('./settings.json');
|
||||||
const privateKey = settings.sslPrivateKey ? fs.readFileSync(settings.sslPrivateKey, 'utf8') : null;
|
const privateKey = settings.sslPrivateKey ? fs.readFileSync(settings.sslPrivateKey, 'utf8') : null;
|
||||||
|
@ -38,267 +33,21 @@ function Server () {
|
||||||
this.connections = 0;
|
this.connections = 0;
|
||||||
this.takenBooks = [];
|
this.takenBooks = [];
|
||||||
|
|
||||||
this.server.use(helmet());
|
require('./routes/middleware')(this);
|
||||||
|
|
||||||
this.server.use(bodyParser.json()); // support json encoded bodies
|
require('./routes/get_home')(this);
|
||||||
this.server.use(bodyParser.urlencoded({ extended: true })); // support encoded bodies
|
|
||||||
|
|
||||||
this.server.use('/give', fileUpload({ // support file uploads
|
require('./routes/get_give')(this);
|
||||||
limits: {
|
require('./routes/post_give')(this);
|
||||||
fileSize: (settings.maxFileSize > 0 ? settings.maxFileSize * 1024 * 1024 : Infinity), // filesize in bytes (settings accepts MB)
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
this.server.use('/tools', fileUpload()); // Allow file upload on backup with no limits.
|
|
||||||
|
|
||||||
this.server.use('/files', express.static(path.join(__dirname, './public/files/')));
|
require('./routes/get_history')(this);
|
||||||
this.server.use('/css', express.static(path.resolve('./node_modules/bulma/css/')));
|
|
||||||
this.server.use('/css', express.static(path.join(__dirname, './public/css/')));
|
|
||||||
this.server.use('/js', express.static(path.join(__dirname, './public/js/')));
|
|
||||||
this.server.use('/js', express.static(path.resolve('./node_modules/jquery/dist/')));
|
|
||||||
this.server.use('/js', express.static(path.resolve('./node_modules/socket.io-client/dist/')));
|
|
||||||
|
|
||||||
// If a `.well-known` directory exists, allow it to be used for things like Let's Encrypt challenges
|
require('./routes/get_about')(this);
|
||||||
if (fs.existsSync(path.resolve('./.well-known'))) {
|
|
||||||
this.server.use('/.well-known', express.static(path.resolve('./.well-known')));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.https && settings.forceHTTPS) {
|
require('./routes/get_tools')(this);
|
||||||
this.server.use(function (req, res, next) {
|
require('./routes/post_tools')(this);
|
||||||
if (!req.secure) {
|
|
||||||
return res.redirect(['https://', req.get('Host'), req.baseUrl].join(''));
|
|
||||||
}
|
|
||||||
next();0
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.server.get('/', (req, res) => {
|
|
||||||
const html = this.generateHomePage(req);
|
|
||||||
if (html) {
|
|
||||||
res.send(html);
|
|
||||||
} else {
|
|
||||||
res.send('Something went wrong!');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.server.get('/give', (req, res) => {
|
require('./routes/socketio')(this);
|
||||||
const resourcePath = (req.url.substr(-1) === '/' ? '../' : './');
|
|
||||||
let body = this.fillTemplate('./templates/pages/uploadForm.html', { resourcePath });
|
|
||||||
body = this.replaceBodyWithTooManyBooksWarning(body);
|
|
||||||
|
|
||||||
const html = this.fillTemplate('./templates/htmlContainer.html', { title: 'Give a Book', resourcePath, body });
|
|
||||||
res.send(html);
|
|
||||||
});
|
|
||||||
this.server.post('/give', (req, res) => {
|
|
||||||
const resourcePath = (req.url.substr(-1) === '/' ? '../' : './');
|
|
||||||
const { title, author, summary, contributor } = req.body;
|
|
||||||
if (Object.keys(req.files).length > 0
|
|
||||||
&& req.body.hasOwnProperty('title') && title.trim() !== ''
|
|
||||||
&& req.body.hasOwnProperty('summary') && summary.trim() !== '') {
|
|
||||||
const { book } = req.files;
|
|
||||||
const fileType = book.name.substr(book.name.lastIndexOf('.'));
|
|
||||||
this.addBook({ book, title, author, summary, contributor, fileType }, () => {
|
|
||||||
const messageBox = this.fillTemplate('./templates/elements/messageBox.html', {
|
|
||||||
style: 'is-success',
|
|
||||||
header: 'Upload Successful',
|
|
||||||
message: 'Thank you for your contribution!'
|
|
||||||
});
|
|
||||||
const modal = this.fillTemplate('./templates/elements/modal.html', {
|
|
||||||
isActive: 'is-active',
|
|
||||||
content: messageBox,
|
|
||||||
});
|
|
||||||
let body = this.fillTemplate('./templates/pages/uploadForm.html', { resourcePath });
|
|
||||||
body = this.replaceBodyWithTooManyBooksWarning(body);
|
|
||||||
const html = this.fillTemplate('./templates/htmlContainer.html', { title: 'Give a Book', resourcePath, body, modal });
|
|
||||||
res.send(html);
|
|
||||||
}, (err) => {
|
|
||||||
const messageBox = this.fillTemplate('./templates/elements/messageBox.html', {
|
|
||||||
style: 'is-danger',
|
|
||||||
header: 'Upload Failed',
|
|
||||||
message: err,
|
|
||||||
});
|
|
||||||
const modal = this.fillTemplate('./templates/elements/modal.html', {
|
|
||||||
isActive: 'is-active',
|
|
||||||
content: messageBox,
|
|
||||||
});
|
|
||||||
let body = this.fillTemplate('./templates/pages/uploadForm.html', { resourcePath, title, author, summary, contributor });
|
|
||||||
body = this.replaceBodyWithTooManyBooksWarning(body);
|
|
||||||
const html = this.fillTemplate('./templates/htmlContainer.html', { title: 'Give a Book', resourcePath, body, modal });
|
|
||||||
res.send(html);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
let errorMessage = '';
|
|
||||||
if (Object.keys(req.files).length <= 0) {
|
|
||||||
errorMessage += 'You have not selected a file.';
|
|
||||||
}
|
|
||||||
if (!req.body.hasOwnProperty('title') || req.body.title.trim() === '') {
|
|
||||||
errorMessage += (errorMessage.length > 0 ? '<br>' : '') + 'You have not written a title.';
|
|
||||||
}
|
|
||||||
if (!req.body.hasOwnProperty('summary') || req.body.summary.trim() === '') {
|
|
||||||
errorMessage += (errorMessage.length > 0 ? '<br>' : '') + 'You have not written a summary.';
|
|
||||||
}
|
|
||||||
const message = this.fillTemplate('./templates/elements/messageBox.html', {
|
|
||||||
style: 'is-danger',
|
|
||||||
header: 'Missing Required Fields',
|
|
||||||
message: errorMessage,
|
|
||||||
});
|
|
||||||
let body = this.fillTemplate('./templates/pages/uploadForm.html', { resourcePath, title, author, summary, contributor });
|
|
||||||
body = this.replaceBodyWithTooManyBooksWarning(body);
|
|
||||||
const html = this.fillTemplate('./templates/htmlContainer.html', { title: 'Give a Book', resourcePath, body, message });
|
|
||||||
res.send(html);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.server.get('/history', (req, res) => {
|
|
||||||
const html = this.generateHistoryPage(req);
|
|
||||||
if (html) {
|
|
||||||
res.send(html);
|
|
||||||
} else {
|
|
||||||
res.send('Something went wrong!');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.server.get('/about', (req, res) => {
|
|
||||||
const resourcePath = (req.url.substr(-1) === '/' ? '../' : './');
|
|
||||||
const body = this.fillTemplate('./templates/pages/about.html', { resourcePath });
|
|
||||||
const html = this.fillTemplate('./templates/htmlContainer.html', { title: 'About', body });
|
|
||||||
if (html) {
|
|
||||||
res.send(html);
|
|
||||||
} else {
|
|
||||||
res.send('Something went wrong!');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.server.get('/tools', (req, res) => {
|
|
||||||
if (req.query.pass === settings.toolsPassword) {
|
|
||||||
const templateValues = {};
|
|
||||||
let html = this.fillTemplate('./templates/pages/tools.html', templateValues);
|
|
||||||
|
|
||||||
if (req.query.do && ['resetVisitors'].includes(req.query.do)) {
|
|
||||||
this.connections = 0;
|
|
||||||
templateValues.resetVisitors = 'Done!';
|
|
||||||
html = this.fillTemplate('./templates/pages/tools.html', templateValues);
|
|
||||||
res.send(html);
|
|
||||||
} else if (req.query.dl && ['files', 'history'].includes(req.query.dl)) {
|
|
||||||
const onezip = require('onezip');
|
|
||||||
const { dl } = req.query;
|
|
||||||
const saveLocation = path.resolve(this.fileLocation, dl + 'Backup.zip');
|
|
||||||
const backupLocation = dl === 'history' ? this.historyLocation : this.fileLocation;
|
|
||||||
const files = fs.readdirSync(backupLocation).filter(fileName => !fileName.includes('.zip'));
|
|
||||||
onezip.pack(backupLocation, saveLocation, files)
|
|
||||||
.on('start', () => {
|
|
||||||
console.info('Starting a backup zip of ' + dl)
|
|
||||||
})
|
|
||||||
.on('error', (error) => {
|
|
||||||
console.error(error);
|
|
||||||
templateValues[dl + 'Download'] = 'Something went wrong: ' + JSON.stringify(error);
|
|
||||||
html = this.fillTemplate('./templates/pages/tools.html', templateValues);
|
|
||||||
res.send(html);
|
|
||||||
})
|
|
||||||
.on('end', () => {
|
|
||||||
console.log('Backup complete. Saved to ' + saveLocation);
|
|
||||||
let backupLocation = saveLocation.replace(/\\/g, '/');
|
|
||||||
backupLocation = backupLocation.substr(backupLocation.lastIndexOf('/'));
|
|
||||||
templateValues[dl + 'Download'] = '<a download href="' + encodeURI('./files' + backupLocation) + '">Download</a> (This will be removed from the server in 1 hour)';
|
|
||||||
html = this.fillTemplate('./templates/pages/tools.html', templateValues);
|
|
||||||
res.send(html);
|
|
||||||
console.log('Will delete ' + saveLocation + ' in 1 hour');
|
|
||||||
setTimeout(() => {
|
|
||||||
fs.unlink(saveLocation, (err) => {
|
|
||||||
if (err) {
|
|
||||||
console.error(err);
|
|
||||||
} else {
|
|
||||||
console.log('Deleted backup file ' + saveLocation);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}, 60 * 60 * 1000);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
res.send(html);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
res.status(400).send();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.server.post('/tools', (req, res) => {
|
|
||||||
if (req.query.pass === settings.toolsPassword) {
|
|
||||||
const templateValues = {};
|
|
||||||
let html = this.fillTemplate('./templates/pages/tools.html', templateValues);
|
|
||||||
|
|
||||||
const { files } = req;
|
|
||||||
if (Object.keys(files).length > 0) {
|
|
||||||
const backupType = Object.keys(files)[0];
|
|
||||||
if (['files', 'history'].includes(backupType)) {
|
|
||||||
const onezip = require('onezip');
|
|
||||||
const uploadPath = path.resolve('./', backupType + 'UploadedBackup.zip');
|
|
||||||
files[backupType].mv(uploadPath, (err) => {
|
|
||||||
if (err) {
|
|
||||||
console.error(error);
|
|
||||||
templateValues[backupType + 'UploadSuccess'] = 'Could not upload the file.';
|
|
||||||
html = this.fillTemplate('./templates/pages/tools.html', templateValues);
|
|
||||||
res.send(html);
|
|
||||||
} else {
|
|
||||||
onezip.extract(uploadPath, path.resolve('./public', backupType))
|
|
||||||
.on('start', () => {
|
|
||||||
console.info('Extracting file ' + uploadPath)
|
|
||||||
})
|
|
||||||
.on('error', (error) => {
|
|
||||||
console.error(error);
|
|
||||||
templateValues[backupType + 'UploadSuccess'] = 'Something went wrong: ' + JSON.stringify(error);
|
|
||||||
html = this.fillTemplate('./templates/pages/tools.html', templateValues);
|
|
||||||
res.send(html);
|
|
||||||
})
|
|
||||||
.on('end', () => {
|
|
||||||
templateValues[backupType + 'UploadSuccess'] = 'Uploaded Successfully!';
|
|
||||||
html = this.fillTemplate('./templates/pages/tools.html', templateValues);
|
|
||||||
res.send(html);
|
|
||||||
fs.unlink(uploadPath, (err) => {
|
|
||||||
if (err) {
|
|
||||||
console.error(err);
|
|
||||||
} else {
|
|
||||||
console.log('Deleted backup file ' + uploadPath);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
templateValues['generalError'] = '<p>' + backupType + ' is not a valid backup type.</p>';
|
|
||||||
html = this.fillTemplate('./templates/pages/tools.html', templateValues);
|
|
||||||
res.send(html);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
res.send(html);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
res.status(400).send();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.io.on('connection', socket => {
|
|
||||||
if (!settings.hideVisitors) {
|
|
||||||
this.connections++;
|
|
||||||
this.io.emit('update visitors', this.connections);
|
|
||||||
}
|
|
||||||
|
|
||||||
socket.on('take book', bookId => {
|
|
||||||
const fileLocation = this.takeBook(bookId, socket.id);
|
|
||||||
if (fileLocation) {
|
|
||||||
console.log(socket.id + ' removed ' + bookId);
|
|
||||||
const downloadLocation = fileLocation.substr(fileLocation.lastIndexOf('/'));
|
|
||||||
socket.emit('get book', encodeURI('./files' + downloadLocation));
|
|
||||||
socket.broadcast.emit('remove book', bookId);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on('disconnect', () => {
|
|
||||||
if (!settings.hideVisitors) {
|
|
||||||
this.connections--;
|
|
||||||
if (this.connections < 0) this.connections = 0;
|
|
||||||
this.io.emit('update visitors', this.connections);
|
|
||||||
}
|
|
||||||
this.deleteBooks(socket.id);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Server.prototype.fillTemplate = function (file, templateVars = {}) {
|
Server.prototype.fillTemplate = function (file, templateVars = {}) {
|
||||||
|
@ -306,7 +55,7 @@ Server.prototype.fillTemplate = function (file, templateVars = {}) {
|
||||||
if (this.templateCache.hasOwnProperty(file)) {
|
if (this.templateCache.hasOwnProperty(file)) {
|
||||||
data = this.templateCache[file];
|
data = this.templateCache[file];
|
||||||
} else {
|
} else {
|
||||||
data = fs.readFileSync(path.join(__dirname, file), 'utf8');
|
data = fs.readFileSync(path.resolve(file), 'utf8');
|
||||||
}
|
}
|
||||||
if (data) {
|
if (data) {
|
||||||
if (!this.templateCache.hasOwnProperty(file)) {
|
if (!this.templateCache.hasOwnProperty(file)) {
|
||||||
|
@ -347,118 +96,6 @@ Server.prototype.replaceBodyWithTooManyBooksWarning = function (body) {
|
||||||
return body;
|
return body;
|
||||||
}
|
}
|
||||||
|
|
||||||
Server.prototype.generateHomePage = function (req) {
|
|
||||||
const files = fs.readdirSync(this.fileLocation).filter(fileName => fileName.includes('.json'))
|
|
||||||
.map(fileName => { // Cache the file data so sorting doesn't need to re-check each file
|
|
||||||
return { name: fileName, time: fs.statSync(path.resolve(this.fileLocation, fileName)).mtime.getTime() };
|
|
||||||
}).sort((a, b) => a.time - b.time).map(v => v.name); // Sort from oldest to newest.
|
|
||||||
|
|
||||||
let books = files.map(fileName => {
|
|
||||||
const bookData = JSON.parse(fs.readFileSync(path.resolve(this.fileLocation, fileName), 'utf8'));
|
|
||||||
if (bookData.hasOwnProperty('fileName')) return '';
|
|
||||||
bookData.author = bookData.author ? bookData.author : '<em>author not provided</em>';
|
|
||||||
bookData.contributor = bookData.contributor ? bookData.contributor : 'Anonymous';
|
|
||||||
|
|
||||||
const id = fileName.replace('.json', '');
|
|
||||||
const confirmId = 'confirm_' + id;
|
|
||||||
const added = fecha.format(new Date(bookData.added), 'hh:mm:ssA on dddd MMMM Do, YYYY');
|
|
||||||
const modal = this.fillTemplate('./templates/elements/modalCard.html', {
|
|
||||||
id,
|
|
||||||
header: '<h2 class="title">' + bookData.title + '</h2><h4 class="subtitle">' + bookData.author + '</h4>',
|
|
||||||
content: this.fillTemplate('./templates/elements/bookInfo.html', {
|
|
||||||
contributor: bookData.contributor,
|
|
||||||
fileFormat: bookData.fileType,
|
|
||||||
added,
|
|
||||||
summary: snarkdown(bookData.summary),
|
|
||||||
})
|
|
||||||
+ this.fillTemplate('./templates/elements/modal.html', {
|
|
||||||
id: confirmId,
|
|
||||||
content: this.fillTemplate('./templates/elements/messageBox.html', {
|
|
||||||
header: 'Download Your Book',
|
|
||||||
message: this.fillTemplate('./templates/elements/takeConfirm.html', { id }),
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
footer: '<a class="button close">Close</a> <a class="button is-success modal-button" data-modal="' + confirmId + '">Take Book</a>',
|
|
||||||
});
|
|
||||||
return this.fillTemplate('./templates/elements/book.html', {
|
|
||||||
id,
|
|
||||||
title: bookData.title,
|
|
||||||
author: bookData.author,
|
|
||||||
fileType: bookData.fileType,
|
|
||||||
modal,
|
|
||||||
});
|
|
||||||
}).join('');
|
|
||||||
|
|
||||||
if (books == '') {
|
|
||||||
books = '<div class="column"><div class="content">The shelf is empty. Would you like to <a href="/give">add a book</a>?</div></div>';
|
|
||||||
}
|
|
||||||
|
|
||||||
const body = '<h2 class="title">Available Books</h2><div class="columns is-multiline">' + books + '</div>';
|
|
||||||
return this.fillTemplate('./templates/htmlContainer.html', {
|
|
||||||
title: 'View',
|
|
||||||
resourcePath: (req.url.substr(-1) === '/' ? '../' : './'),
|
|
||||||
body
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Server.prototype.generateHistoryPage = function (req) {
|
|
||||||
const files = fs.readdirSync(this.historyLocation).filter(fileName => fileName.includes('.json'))
|
|
||||||
.map(fileName => { // Cache the file data so sorting doesn't need to re-check each file
|
|
||||||
return { name: fileName, time: fs.statSync(path.resolve(this.historyLocation, fileName)).mtime.getTime() };
|
|
||||||
}).sort((a, b) => b.time - a.time).map(v => v.name); // Sort from newest to oldest.
|
|
||||||
|
|
||||||
let history = files.map(fileName => {
|
|
||||||
const bookData = JSON.parse(fs.readFileSync(path.resolve(this.historyLocation, fileName), 'utf8'));
|
|
||||||
bookData.author = bookData.author ? bookData.author : '<em>author not provided</em>';
|
|
||||||
bookData.contributor = bookData.contributor ? bookData.contributor : 'Anonymous';
|
|
||||||
const id = fileName.replace('.json', '');
|
|
||||||
const added = fecha.format(new Date(bookData.added), 'hh:mm:ssA on dddd MMMM Do, YYYY');
|
|
||||||
const removed = fecha.format(new Date(parseInt(id)), 'hh:mm:ssA on dddd MMMM Do, YYYY');
|
|
||||||
const removedTag = '<div class="control"><div class="tags has-addons"><span class="tag">Taken</span><span class="tag is-warning">' + removed + '</span></div></div>';
|
|
||||||
const modal = this.fillTemplate('./templates/elements/modalCard.html', {
|
|
||||||
id,
|
|
||||||
header: '<h2 class="title">' + bookData.title + '</h2><h4 class="subtitle">' + bookData.author + '</h4>',
|
|
||||||
content: this.fillTemplate('./templates/elements/bookInfo.html', {
|
|
||||||
contributor: bookData.contributor,
|
|
||||||
fileFormat: bookData.fileType,
|
|
||||||
added,
|
|
||||||
removedTag,
|
|
||||||
summary: snarkdown(bookData.summary),
|
|
||||||
}),
|
|
||||||
footer: '<a class="button close">Close</a>',
|
|
||||||
});
|
|
||||||
return this.fillTemplate('./templates/elements/book.html', {
|
|
||||||
id,
|
|
||||||
title: bookData.title,
|
|
||||||
author: bookData.author,
|
|
||||||
fileType: bookData.fileType,
|
|
||||||
modal,
|
|
||||||
});
|
|
||||||
}).join('');
|
|
||||||
|
|
||||||
if (history == '') {
|
|
||||||
history = '<div class="column"><div class="content">No books have been taken yet. Would you like to <a href="/">take a book</a>?</div></div>';
|
|
||||||
}
|
|
||||||
|
|
||||||
const body = '<h2 class="title">History</h2><div class="columns is-multiline">' + history + '</div>';
|
|
||||||
return this.fillTemplate('./templates/htmlContainer.html', {
|
|
||||||
title: 'History',
|
|
||||||
resourcePath: (req.url.substr(-1) === '/' ? '../' : './'),
|
|
||||||
body
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Server.prototype.start = function () {
|
|
||||||
this.http.listen((process.env.PORT || settings.port), () => {
|
|
||||||
console.log('Started server on port ' + (process.env.PORT || settings.port));
|
|
||||||
});
|
|
||||||
if (this.https) {
|
|
||||||
this.https.listen(443, () => {
|
|
||||||
console.log('Started SSL server on port 443');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Server.prototype.addBook = function (uploadData = {}, success = () => {}, error = () => {}) {
|
Server.prototype.addBook = function (uploadData = {}, success = () => {}, error = () => {}) {
|
||||||
const { book } = uploadData;
|
const { book } = uploadData;
|
||||||
|
|
||||||
|
@ -568,5 +205,16 @@ Server.prototype.uuid4 = function () {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Server.prototype.start = function () {
|
||||||
|
this.http.listen((process.env.PORT || settings.port), () => {
|
||||||
|
console.log('Started server on port ' + (process.env.PORT || settings.port));
|
||||||
|
});
|
||||||
|
if (this.https) {
|
||||||
|
this.https.listen(443, () => {
|
||||||
|
console.log('Started SSL server on port 443');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const server = new Server();
|
const server = new Server();
|
||||||
server.start();
|
server.start();
|
||||||
|
|
Loading…
Reference in New Issue