Skip to content

Instantly share code, notes, and snippets.

@papnkukn
Created October 2, 2022 07:55
Show Gist options
  • Select an option

  • Save papnkukn/2bc439126336fd5b01819dfcd692b54e to your computer and use it in GitHub Desktop.

Select an option

Save papnkukn/2bc439126336fd5b01819dfcd692b54e to your computer and use it in GitHub Desktop.
A very simple pastebin clone. Pure Node.js with no external dependencies.
/*** A very simple pastebin clone ***/
const fs = require('fs');
const path = require('path');
const http = require('http');
const host = process.env.NODE_HOST || "0.0.0.0";
const port = process.env.NODE_PORT || 3000;
const datadir = process.env.NODE_DATA_DIR || "data";
if (!fs.existsSync(datadir)) {
console.error("Data directory does not exist: " + datadir);
process.exit(1);
}
function htmlencode(c) {
c = c.replace(/&/g, '&');
c = c.replace(/</g, '&lt;');
c = c.replace(/>/g, '&gt;');
return c;
}
function regex(url) {
let m = /^\/?([A-Za-z0-9\-]+)$/g.exec(url);
return m ? m[1] : null;
}
function render(model) {
let content = htmlencode(model.content);
let html = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/css/bootstrap.min.css" integrity="sha384-xOolHFLEh07PJGoPkLv1IbcEPTNtaed2xpHsD9ESMhqIYd0nLMwNLD69Npy4HI+N" crossorigin="anonymous">
<title>Pastebin</title>
</head>
<body style="background: #eee;">
<form action="/${model.id}" method="post">
<div class="container-fluid mt-3">
<div class="row">
<div class="col-12 d-flex">
<h3 class="text-monospace">Pastebin <a href="/${model.id}" class="text-primary text-monospace">${model.id}</a></h3>
<div class="ml-auto">
<a href="/" class="btn btn-sm btn-outline-primary" accesskey="q">New</a>
<button class="btn btn-sm btn-success" type="submit" accesskey="s">Save</button>
</div>
</div>
<div class="col-12">
<div class="form-group">
<textarea autofocus name="content" placeholder="Paste some text..." rows="35" class="form-control text-monospace w-100 h-100">${content}</textarea>
</div>
</div>
</div>
</div>
</form>
</body>
</html>`;
return html;
}
const server = http.createServer(function(request, response) {
//Debug: console.log(request.method + " " + request.url);
//Index page: GET /
if (request.method == "GET" && request.url == "/") {
let id = Math.random().toString(36).substr(2, 9);
let model = { id, mode: "new", content: "" };
response.writeHead(200, { "Content-Type": "text/html" });
return response.end(render(model));
}
//View content: GET /:id where :id can be A-Z, a-z, 0-9, and -
if (request.method == "GET" && regex(request.url)) {
let id = regex(request.url);
let file = path.join(datadir, id) + ".txt";
let content = fs.existsSync(file) ? fs.readFileSync(file, "utf-8") : "";
let model = { id, mode: "edit", content };
response.writeHead(200, { "Content-Type": "text/html" });
return response.end(render(model));
}
//Save content: POST /:id where :id can be A-Z, a-z, 0-9, and -
if (request.method == "POST" && regex(request.url)) {
let id = regex(request.url);
let body = "";
request.on('data', function(data) {
body += data;
});
request.on('end', function() {
let value = body.substring("content=".length);
let content = decodeURIComponent(value).replace(/\+/g, " ");
let file = path.join(datadir, id) + ".txt";
fs.writeFileSync(file, content);
response.writeHead(302, { "Location": "/" + id });
return response.end();
});
return;
}
//Delete content: DELETE /:id where :id can be A-Z, a-z, 0-9, and -
if (request.method == "DELETE" && regex(request.url)) {
let id = regex(request.url);
let file = path.join(datadir, id) + ".txt";
fs.existsSync(file) ? fs.unlinkSync(file) : null;
response.writeHead(302, { "Location": "/" });
return response.end();
}
//404 Not Found
response.writeHead(404, { "Content-Type": "text/plain" });
response.end("404 Not Found");
});
server.listen(port, host);
console.log(`Listening at http://${host}:${port}`);
@papnkukn
Copy link
Copy Markdown
Author

papnkukn commented Oct 2, 2022

Getting started

Download server.js and install Node.js

Create the content storage directory data

mkdir data && chmod +w data

Start HTTP server on port 3000

node server.js

Open in a web browser

http://localhost:3000

Advanced options

Environmental variables

NODE_HOST=0.0.0.0
NODE_PORT=3000
NODE_DATA_DIR=data

To run the process with environmental variables

NODE_HOST=localhost NODE_PORT=3024 NODE_DATA_DIR=/home/pi/pastebin node server.js

Clean up

Create a shell script and run it periodically to delete content older than 7 days:

#!/bin/bash

DATA_DIR=/home/pi/pastebin
EXPIRE_DAYS=7

for i in $(find $DATA_DIR -type f -mtime +$EXPIRE_DAYS -printf '%f\n' | grep ".txt$" | sed 's/\..*//' | sort | uniq); do
  echo "Removing outdated $i"
  rm -f $DATA_DIR/$i.txt
done

@papnkukn
Copy link
Copy Markdown
Author

papnkukn commented Oct 2, 2022

Screenshot
screenshot

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment