mirror of
https://github.com/justinian/postmortem.git
synced 2025-12-09 16:14:31 -08:00
Initial commit
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
__pycache__
|
||||||
|
/venv
|
||||||
88
poem.py
Normal file
88
poem.py
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
from flask import Flask
|
||||||
|
from pyhash import fnv1a_32
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
hash = fnv1a_32()
|
||||||
|
|
||||||
|
ROUND_TRIP = 29132
|
||||||
|
KM_TO_MI = 0.6213712
|
||||||
|
num_format = "{:,.0f}"
|
||||||
|
|
||||||
|
def validate_key(key):
|
||||||
|
from os.path import exists
|
||||||
|
from flask import abort
|
||||||
|
|
||||||
|
if not key:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
int(key, base=16)
|
||||||
|
except ValueError:
|
||||||
|
abort(400)
|
||||||
|
|
||||||
|
if not exists(f"poems/{key}.txt"):
|
||||||
|
abort(404)
|
||||||
|
|
||||||
|
def response_plain(text):
|
||||||
|
from flask import make_response
|
||||||
|
resp = make_response(text)
|
||||||
|
resp.headers["Content-type"] = "text/plain"
|
||||||
|
return resp
|
||||||
|
|
||||||
|
@app.route("/", methods=["GET"])
|
||||||
|
@app.route("/<key>", methods=["GET"])
|
||||||
|
def poem_view(key=""):
|
||||||
|
from flask import render_template
|
||||||
|
validate_key(key)
|
||||||
|
|
||||||
|
poem = ""
|
||||||
|
stats = {}
|
||||||
|
if key:
|
||||||
|
poem = open(f"poems/{key}.txt", 'r', encoding='utf-8').read()
|
||||||
|
lines = poem.splitlines()[1:]
|
||||||
|
words = sum([len(l.split()) for l in lines])
|
||||||
|
breaks = len(lines) - 1
|
||||||
|
posts = words + breaks
|
||||||
|
km = posts * ROUND_TRIP
|
||||||
|
mi = km * KM_TO_MI
|
||||||
|
|
||||||
|
stats = dict(words=words, lines=breaks,
|
||||||
|
roundtrip=num_format.format(ROUND_TRIP),
|
||||||
|
km=num_format.format(km),
|
||||||
|
mi=num_format.format(mi),
|
||||||
|
)
|
||||||
|
|
||||||
|
return render_template("index.html", key=key, poem=poem, stats=stats)
|
||||||
|
|
||||||
|
@app.route("/poem/<key>", methods=["GET"])
|
||||||
|
def poem_get(key):
|
||||||
|
validate_key(key)
|
||||||
|
|
||||||
|
if not key:
|
||||||
|
return response_plain("")
|
||||||
|
return response_plain(open(filename, 'r', encoding='utf-8').read())
|
||||||
|
|
||||||
|
@app.route("/poem/", methods=["POST"])
|
||||||
|
@app.route("/poem/<key>", methods=["POST"])
|
||||||
|
def poem_post(key=""):
|
||||||
|
from flask import request
|
||||||
|
|
||||||
|
validate_key(key)
|
||||||
|
|
||||||
|
poem = ""
|
||||||
|
if key:
|
||||||
|
poem = open(f"poems/{key}.txt", 'r', encoding='utf-8').read()
|
||||||
|
|
||||||
|
addend = request.get_data(as_text=True)
|
||||||
|
if addend != "\n":
|
||||||
|
addend = addend.split()[0]
|
||||||
|
poem = f"{poem}{addend} "
|
||||||
|
else:
|
||||||
|
poem += "\n"
|
||||||
|
|
||||||
|
newhash = "{0:08x}".format(hash(poem.encode('utf-8')))
|
||||||
|
|
||||||
|
with open(f"poems/{newhash}.txt", 'w', encoding='utf-8') as f:
|
||||||
|
f.write(poem)
|
||||||
|
|
||||||
|
return response_plain(newhash)
|
||||||
1
poems/.gitignore
vendored
Normal file
1
poems/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
*.txt
|
||||||
2
requirements.txt
Normal file
2
requirements.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
flask == 2.2
|
||||||
|
pyhash == 0.9
|
||||||
26
static/main.js
Normal file
26
static/main.js
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
function update(key, word) {
|
||||||
|
let url = "/poem/" + key;
|
||||||
|
return fetch(url, {
|
||||||
|
"method": "POST",
|
||||||
|
"body": word,
|
||||||
|
})
|
||||||
|
.then( resp => resp.text() )
|
||||||
|
.then( text => {
|
||||||
|
console.log("new key", text);
|
||||||
|
return text;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function update_all(starting_key, words) {
|
||||||
|
var promise = Promise.resolve(starting_key);
|
||||||
|
for (const word of words) {
|
||||||
|
if (word.length < 1) continue;
|
||||||
|
promise = promise.then( key => {
|
||||||
|
return update(key, word);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return promise.then( key => {
|
||||||
|
return update(key, "\n");
|
||||||
|
});
|
||||||
|
}
|
||||||
BIN
static/route.png
Executable file
BIN
static/route.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 2.0 MiB |
111
templates/index.html
Normal file
111
templates/index.html
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<link rel="stylesheet" href="https://cdn.simplecss.org/simple.min.css">
|
||||||
|
<title>POST mortem</title>
|
||||||
|
<style>
|
||||||
|
.post {
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.message {
|
||||||
|
font-size: 1.3rem;
|
||||||
|
margin: 1rem 1rem 3rem 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.bordered {
|
||||||
|
margin-top: 2rem;
|
||||||
|
padding: 0.8rem;
|
||||||
|
|
||||||
|
width: 80%;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 10px;
|
||||||
|
background: #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
blockquote.poem {
|
||||||
|
white-space: pre;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
blockquote.poem::first-line {
|
||||||
|
font-size: 1.8rem;
|
||||||
|
font-weight: bold;
|
||||||
|
text-decoration: underline;
|
||||||
|
margin-bottom: 3rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1><span class="post">POST</span> mortem</h1>
|
||||||
|
|
||||||
|
{% if poem %}
|
||||||
|
<blockquote class="poem" id="poem-quote">{{ poem }}</blockquote>
|
||||||
|
{% else %}
|
||||||
|
<div class="message" style="font-style: italic;">
|
||||||
|
No poem has been started yet, add some words!
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<form id="add-words-form">
|
||||||
|
<div>
|
||||||
|
<label for="addend-input">Enter some words to add:</label>
|
||||||
|
<input type="text" id="addend-input" style="width: 80%;" />
|
||||||
|
</div>
|
||||||
|
<button type="submit">Add Words</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<script type="module">
|
||||||
|
import { update_all } from '/static/main.js';
|
||||||
|
|
||||||
|
let form = document.getElementById('add-words-form');
|
||||||
|
let input = document.getElementById('addend-input');
|
||||||
|
|
||||||
|
form.addEventListener('submit', evt => {
|
||||||
|
evt.preventDefault();
|
||||||
|
|
||||||
|
var key = "{{ key }}";
|
||||||
|
let words = input.value.split(" ");
|
||||||
|
update_all(key, words).then( key => {
|
||||||
|
document.location = "/" + key;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{% if key %}
|
||||||
|
<div class="bordered">
|
||||||
|
<strong>Mail this URL on to the next person!</strong>
|
||||||
|
<div id="url-div" style="float: left;"></div>
|
||||||
|
<button id="copy-button" style="float: right;">Copy to Clipboard</button>
|
||||||
|
</div>
|
||||||
|
<script type="module">
|
||||||
|
let div = document.getElementById('url-div');
|
||||||
|
let url = document.location;
|
||||||
|
div.innerHTML = `<a href="${url}">${url}</div>`;
|
||||||
|
|
||||||
|
let button = document.getElementById('copy-button');
|
||||||
|
button.addEventListener('click', evt => {
|
||||||
|
navigator.clipboard.writeText(url);
|
||||||
|
button.innerHTML = "Copied!";
|
||||||
|
button.disabled = true;
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
button.innerHTML = "Copy to Clipboard";
|
||||||
|
button.disabled = false;
|
||||||
|
}, 2000);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="bordered">
|
||||||
|
<strong><span class="post">POST</span> distance</strong>
|
||||||
|
<table style="width: 100%">
|
||||||
|
<tr><td>Number of words (excl. title)</td><td>{{ stats.words }}</td></tr>
|
||||||
|
<tr><td>Number of newlines (excl. title)</td><td>{{ stats.lines }}</td></tr>
|
||||||
|
<tr><td>Round-trip distance</td><td>{{ stats.roundtrip }} km</td></tr>
|
||||||
|
<tr><td>Total distance</td><td><strong>{{ stats.km }} km</strong> ({{ stats.mi }} mi)</td></tr>
|
||||||
|
</table>
|
||||||
|
<img style="width: 100%" src="static/route.png">
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user