mirror of
https://github.com/MrUnknownDE/unknownbin.git
synced 2026-04-19 22:33:43 +02:00
add dynamic seo settings
This commit is contained in:
@@ -11,6 +11,7 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"clean-css": "^5.3.3",
|
"clean-css": "^5.3.3",
|
||||||
|
"ejs": "^3.1.10",
|
||||||
"express": "^4.19.2",
|
"express": "^4.19.2",
|
||||||
"helmet": "^7.1.0",
|
"helmet": "^7.1.0",
|
||||||
"uglify-js": "^3.17.4",
|
"uglify-js": "^3.17.4",
|
||||||
|
|||||||
65
server.js
65
server.js
@@ -4,6 +4,7 @@ const path = require('path');
|
|||||||
const express = require('express');
|
const express = require('express');
|
||||||
const helmet = require('helmet');
|
const helmet = require('helmet');
|
||||||
const winston = require('winston');
|
const winston = require('winston');
|
||||||
|
const ejs = require('ejs');
|
||||||
|
|
||||||
const DocumentHandler = require('./lib/document_handler.js');
|
const DocumentHandler = require('./lib/document_handler.js');
|
||||||
const FileStorage = require('./lib/file_storage.js');
|
const FileStorage = require('./lib/file_storage.js');
|
||||||
@@ -37,25 +38,6 @@ logger.info('Welcome to unknownBIN!');
|
|||||||
// init file-storage
|
// init file-storage
|
||||||
const fileStorage = new FileStorage({ path: config.dataPath, logger: logger });
|
const fileStorage = new FileStorage({ path: config.dataPath, logger: logger });
|
||||||
|
|
||||||
// load static documents into file-storage
|
|
||||||
if (config.documents) {
|
|
||||||
for (const name in config.documents) {
|
|
||||||
const docPath = config.documents[name];
|
|
||||||
try {
|
|
||||||
const data = fs.readFileSync(docPath, 'utf8');
|
|
||||||
fileStorage.set(name, data, (success) => {
|
|
||||||
if (success) {
|
|
||||||
logger.verbose(`Loaded document: ${name} from ${docPath}`);
|
|
||||||
} else {
|
|
||||||
logger.warn(`Failed to store document: ${name}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
logger.warn(`Unable to find or read document: ${name} at ${docPath}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// configure the document handler
|
// configure the document handler
|
||||||
const documentHandler = new DocumentHandler({
|
const documentHandler = new DocumentHandler({
|
||||||
store: fileStorage,
|
store: fileStorage,
|
||||||
@@ -68,9 +50,14 @@ const documentHandler = new DocumentHandler({
|
|||||||
// setup routes and request-handling
|
// setup routes and request-handling
|
||||||
const app = express();
|
const app = express();
|
||||||
|
|
||||||
|
// Configure EJS as the view engine
|
||||||
|
app.set('view engine', 'ejs');
|
||||||
|
app.set('views', path.join(__dirname, 'views'));
|
||||||
|
|
||||||
// Use helmet for basic security headers
|
// Use helmet for basic security headers
|
||||||
app.use(helmet());
|
app.use(helmet());
|
||||||
|
|
||||||
|
// API routes
|
||||||
app.get('/raw/:id', (req, res) => {
|
app.get('/raw/:id', (req, res) => {
|
||||||
return documentHandler.handleRawGet(req.params.id, res);
|
return documentHandler.handleRawGet(req.params.id, res);
|
||||||
});
|
});
|
||||||
@@ -81,17 +68,47 @@ app.get('/documents/:id', (req, res) => {
|
|||||||
return documentHandler.handleGet(req.params.id, res);
|
return documentHandler.handleGet(req.params.id, res);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Static files
|
||||||
app.use(express.static(path.join(__dirname, 'static')));
|
app.use(express.static(path.join(__dirname, 'static')));
|
||||||
|
|
||||||
app.get('/:id', (req, res, next) => {
|
// Frontend routes (SSR)
|
||||||
res.sendFile(path.join(__dirname, '/static/index.html'));
|
app.get('/:id', (req, res) => {
|
||||||
|
const key = req.params.id;
|
||||||
|
|
||||||
|
// Don't treat static files as paste IDs
|
||||||
|
if (key.includes('.')) {
|
||||||
|
return res.status(404).send('Not Found');
|
||||||
|
}
|
||||||
|
|
||||||
|
fileStorage.get(key, (data) => {
|
||||||
|
if (data) {
|
||||||
|
// Paste found, render it with dynamic SEO tags
|
||||||
|
const title = `Paste ${key} - unknownBIN`;
|
||||||
|
const description = data.substring(0, 160).replace(/\n/g, ' ');
|
||||||
|
res.render('index', {
|
||||||
|
paste: {
|
||||||
|
key: key,
|
||||||
|
data: data,
|
||||||
|
title: title,
|
||||||
|
description: description
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Paste not found, render the homepage
|
||||||
|
res.render('index', {
|
||||||
|
paste: null
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get('/', (req, res, next) => {
|
app.get('/', (req, res) => {
|
||||||
res.sendFile(path.join(__dirname, '/static/index.html'));
|
// Render the homepage
|
||||||
|
res.render('index', {
|
||||||
|
paste: null
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
app.listen(config.port, config.host, () => {
|
app.listen(config.port, config.host, () => {
|
||||||
logger.info(`Listening on ${config.host}:${config.port}`);
|
logger.info(`Listening on ${config.host}:${config.port}`);
|
||||||
});
|
});
|
||||||
@@ -53,20 +53,40 @@ Haste.prototype.newDocument = function(hideHistory) {
|
|||||||
|
|
||||||
// load an existing document
|
// load an existing document
|
||||||
Haste.prototype.loadDocument = function(key) {
|
Haste.prototype.loadDocument = function(key) {
|
||||||
const _this = this;
|
// Check if data is pre-rendered by the server
|
||||||
_this.doc = new HasteDocument();
|
const $preloaded = $('#preloaded-data');
|
||||||
_this.doc.load(key, function(ret) {
|
if ($preloaded.length > 0 && $preloaded.text().trim().length > 0) {
|
||||||
if (ret) {
|
this.doc = new HasteDocument();
|
||||||
_this.$code.html(ret.value);
|
this.doc.locked = true;
|
||||||
_this.setTitle(ret.key);
|
this.doc.key = key;
|
||||||
window.history.pushState(null, `${_this.appName}-${ret.key}`, `/${ret.key}`);
|
this.doc.data = $preloaded.text();
|
||||||
_this.fullKey();
|
const high = hljs.highlightAuto(this.doc.data).value;
|
||||||
_this.$textarea.val('').hide();
|
const ret = {
|
||||||
_this.$box.show();
|
value: high.replace(/.+/g, "<span class=\"line\">$&</span>").replace(/^\s*[\r\n]/gm, "<span class=\"line\"></span>\n"),
|
||||||
} else {
|
key: key,
|
||||||
_this.newDocument();
|
};
|
||||||
}
|
this.$code.html(ret.value);
|
||||||
});
|
this.setTitle(ret.key);
|
||||||
|
this.fullKey();
|
||||||
|
this.$textarea.val('').hide();
|
||||||
|
this.$box.show();
|
||||||
|
} else {
|
||||||
|
// Data is not pre-rendered, fetch from API
|
||||||
|
const _this = this;
|
||||||
|
_this.doc = new HasteDocument();
|
||||||
|
_this.doc.load(key, function(ret) {
|
||||||
|
if (ret) {
|
||||||
|
_this.$code.html(ret.value);
|
||||||
|
_this.setTitle(ret.key);
|
||||||
|
window.history.pushState(null, `${_this.appName}-${ret.key}`, `/${ret.key}`);
|
||||||
|
_this.fullKey();
|
||||||
|
_this.$textarea.val('').hide();
|
||||||
|
_this.$box.show();
|
||||||
|
} else {
|
||||||
|
_this.newDocument();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// duplicate the current document
|
// duplicate the current document
|
||||||
@@ -83,7 +103,10 @@ Haste.prototype.lockDocument = function() {
|
|||||||
const _this = this;
|
const _this = this;
|
||||||
this.doc.save(this.$textarea.val(), function(err, ret) {
|
this.doc.save(this.$textarea.val(), function(err, ret) {
|
||||||
if (!err && ret) {
|
if (!err && ret) {
|
||||||
_this.$code.html(ret.value.trim().replace(/.+/g, "<span class=\"line\">$&</span>").replace(/^\s*[\r\n]/gm, "<span class=\"line\"></span>\n"));
|
// Manually highlight and display after saving
|
||||||
|
const high = hljs.highlightAuto(ret.data).value;
|
||||||
|
const formatted = high.replace(/.+/g, "<span class=\"line\">$&</span>").replace(/^\s*[\r\n]/gm, "<span class=\"line\"></span>\n");
|
||||||
|
_this.$code.html(formatted);
|
||||||
_this.setTitle(ret.key);
|
_this.setTitle(ret.key);
|
||||||
window.history.pushState(null, `${_this.appName}-${ret.key}`, `/${ret.key}`);
|
window.history.pushState(null, `${_this.appName}-${ret.key}`, `/${ret.key}`);
|
||||||
_this.fullKey();
|
_this.fullKey();
|
||||||
@@ -114,7 +137,7 @@ Haste.prototype.configureButtons = function() {
|
|||||||
return evt.ctrlKey && evt.keyCode === 32;
|
return evt.ctrlKey && evt.keyCode === 32;
|
||||||
},
|
},
|
||||||
action: function() {
|
action: function() {
|
||||||
_this.newDocument(!_this.doc.key);
|
window.location.href = '/';
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -172,15 +195,6 @@ const HasteDocument = function() {
|
|||||||
this.locked = false;
|
this.locked = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
// escape HTML-characters
|
|
||||||
HasteDocument.prototype.htmlEscape = function(s) {
|
|
||||||
return s
|
|
||||||
.replace(/&/g, '&')
|
|
||||||
.replace(/>/g, '>')
|
|
||||||
.replace(/</g, '<')
|
|
||||||
.replace(/"/g, '"');
|
|
||||||
};
|
|
||||||
|
|
||||||
// load a document from the server
|
// load a document from the server
|
||||||
HasteDocument.prototype.load = function(key, callback) {
|
HasteDocument.prototype.load = function(key, callback) {
|
||||||
const _this = this;
|
const _this = this;
|
||||||
@@ -207,14 +221,18 @@ HasteDocument.prototype.load = function(key, callback) {
|
|||||||
HasteDocument.prototype.save = function(data, callback) {
|
HasteDocument.prototype.save = function(data, callback) {
|
||||||
if (this.locked) return false;
|
if (this.locked) return false;
|
||||||
|
|
||||||
this.data = data;
|
const _this = this;
|
||||||
|
_this.data = data;
|
||||||
$.ajax('/documents', {
|
$.ajax('/documents', {
|
||||||
type: 'post',
|
type: 'post',
|
||||||
data: data,
|
data: data,
|
||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
contentType: 'text/plain; charset=utf-8',
|
contentType: 'text/plain; charset=utf-8',
|
||||||
success: function(res) {
|
success: function(res) {
|
||||||
new Haste().loadDocument(res.key);
|
_this.locked = true;
|
||||||
|
_this.key = res.key;
|
||||||
|
// Pass the saved data back to the callback
|
||||||
|
callback(null, { key: res.key, data: _this.data });
|
||||||
},
|
},
|
||||||
error: function(res) {
|
error: function(res) {
|
||||||
try {
|
try {
|
||||||
@@ -256,11 +274,11 @@ $(function() {
|
|||||||
|
|
||||||
const app = new Haste();
|
const app = new Haste();
|
||||||
const path = window.location.pathname;
|
const path = window.location.pathname;
|
||||||
if (path === '/') {
|
if (path === '/' || path === '') {
|
||||||
app.newDocument(true);
|
app.newDocument(true);
|
||||||
} else {
|
} else {
|
||||||
app.loadDocument(path.substring(1, path.length));
|
app.loadDocument(path.substring(1));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
hljs.initHighlightingOnLoad();
|
// No need for hljs.initHighlightingOnLoad() as we do it manually
|
||||||
37
views/index.ejs
Normal file
37
views/index.ejs
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
// views/index.ejs
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<% if (paste) { %>
|
||||||
|
<title><%= paste.title %></title>
|
||||||
|
<meta name="description" content="<%= paste.description %>">
|
||||||
|
<meta property="og:title" content="<%= paste.title %>">
|
||||||
|
<meta property="og:description" content="<%= paste.description %>">
|
||||||
|
<% } else { %>
|
||||||
|
<title>unknownBIN - A Secure Pastebin</title>
|
||||||
|
<meta name="description" content="unknownBIN is a secure and modern open-source Pastebin software written in Node.js.">
|
||||||
|
<meta property="og:title" content="unknownBIN - A Secure Pastebin">
|
||||||
|
<meta property="og:description" content="unknownBIN is a secure and modern open-source Pastebin software written in Node.js.">
|
||||||
|
<% } %>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||||
|
<link rel="stylesheet" type="text/css" href="/application.min.css"/>
|
||||||
|
<link rel="stylesheet" type="text/css" href="/highlight.min.css"/>
|
||||||
|
<link rel="shortcut icon" href="/favicon.png">
|
||||||
|
<script src="/jquery.min.js"></script>
|
||||||
|
<script src="/highlight.min.js"></script>
|
||||||
|
<script src="/application.min.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="tools">
|
||||||
|
<div class="save function" title="Save [Ctrl + S]">Save</div>
|
||||||
|
<div class="new function" title="New [Ctrl + Space]">New</div>
|
||||||
|
<div class="duplicate function" title="Duplicate [Ctrl + D]">Duplicate</div>
|
||||||
|
<div class="raw function" title="Raw [Ctrl + Shift + R]">Raw</div>
|
||||||
|
</div>
|
||||||
|
<div id="content">
|
||||||
|
<pre id="code" style="display:none;" tabindex="0"><code <% if (paste) { %>id="preloaded-data"<% } %>><% if (paste) { %><%- paste.data %><% } %></code></pre>
|
||||||
|
<textarea spellcheck="false" style="display:none;"></textarea>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user