Skip to content

Instantly share code, notes, and snippets.

@papnkukn
Created October 2, 2022 07:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save papnkukn/2bc439126336fd5b01819dfcd692b54e to your computer and use it in GitHub Desktop.
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
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
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