Initial commit: Added current work!

This commit is contained in:
Yuki Joou 2023-06-01 11:48:52 +02:00
commit 10b7960dd9
9 changed files with 250 additions and 0 deletions

3
.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
.idea/
__pycache__/
database/data.sqlite

4
.prettierrc Normal file
View file

@ -0,0 +1,4 @@
{
"tabWidth": 4,
"useTabs": true
}

28
README.md Normal file
View file

@ -0,0 +1,28 @@
# GayBook
This is a simple GuestBook software written in Python, using Flask!
It uses a sqlite database for storage, and avoids spam with a simple proof-of-work system
## Running
You'll need python and flask installed on your machine.
First, set up the database
```console
$ cd database/
$ python setup_db.py
```
Then, run the Flask app
```console
$ python app.py
```
To run in debug mode, use
```console
$ FLASK_DEBUG=1 python app.py
```

75
app.py Normal file
View file

@ -0,0 +1,75 @@
import flask
import dataclasses
import sqlite3
import datetime
import hashlib
app = flask.Flask(__name__)
def get_database_connection():
database_connection = sqlite3.connect("database/data.sqlite")
database_connection.row_factory = sqlite3.Row
return database_connection
@dataclasses.dataclass
class Quote:
author: str
content: str
timestamp: datetime.datetime = datetime.datetime.now()
def proof_of_work_is_valid(self, work: str):
to_hash = f"{self.author}\0{self.content}\0{work}"
hashed = hashlib.sha1(to_hash.encode("utf-8")).hexdigest()
return hashed[:5] == "00000"
def get_all_quotes() -> list[Quote]:
db_connection = get_database_connection()
db_quotes = db_connection.execute(
"SELECT author, content, timestamp FROM quotes ORDER BY timestamp DESC"
).fetchall()
db_connection.close()
quotes: list[Quote] = []
for db_quote in db_quotes:
quotes.append(Quote(db_quote["author"], db_quote["content"], db_quote["timestamp"]))
return quotes
def add_quote(new_quote: Quote):
db_connexion = get_database_connection()
db_connexion.execute(
"INSERT INTO quotes (author, content) VALUES (?, ?)",
(new_quote.author, new_quote.content)
)
db_connexion.commit()
db_connexion.close()
@app.route('/', methods=["POST", "GET"])
def index():
message: str = ""
response_code = 200
if flask.request.method == "POST":
form = flask.request.form
if "username" not in form or "message" not in form or "proof-of-work" not in form:
message = "Did not provide username, quote or proof of work."
response_code = 400
else:
quote = Quote(form["username"], form["message"])
if quote.proof_of_work_is_valid(form["proof-of-work"]):
add_quote(Quote(form["username"], form["message"]))
message = "Your quote was added successfully"
else:
message = "You didn't do the work, silly!"
response_code = 400
return flask.render_template("index.html", quotes=get_all_quotes(), message=message), response_code
if __name__ == "__main__":
app.run()

6
database/quotes.sql Normal file
View file

@ -0,0 +1,6 @@
CREATE TABLE IF NOT EXISTS quotes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
author TEXT NOT NULL,
content TEXT NOT NULL
);

9
database/setup_db.py Normal file
View file

@ -0,0 +1,9 @@
import sqlite3
connection = sqlite3.connect("data.sqlite")
with open("quotes.sql") as fp:
connection.execute(fp.read())
connection.commit()
connection.close()

33
static/proof-of-work.js Normal file
View file

@ -0,0 +1,33 @@
const generateButton = document.getElementById("generate-pow");
generateButton.style.display = "block";
const usernameField = document.getElementById("username");
const messageField = document.getElementById("message");
const powField = document.getElementById("proof-of-work");
const hash = (message) =>
crypto.subtle.digest("SHA-1",
(new TextEncoder()).encode(message)
);
const isValidHash = (hashBuffer) =>
Array.from(new Uint8Array(hashBuffer))
.map(byte => byte.toString(16).padStart(2, "0"))
.join("").startsWith("00000");
const messageBase = () => usernameField.value + "\0" + messageField.value + "\0";
generateButton.onclick = async (event) => {
event.preventDefault();
const base = messageBase();
let currentNonce = 0;
powField.disabled = true;
powField.value = "Generating... Please wait";
console.log("Starting PoW generation, this may take a while....");
while (!isValidHash(await hash(base + currentNonce)))
++currentNonce;
console.log("Done!");
powField.value = currentNonce;
powField.disabled = false;
};

29
static/style.css Normal file
View file

@ -0,0 +1,29 @@
body {
font-family: monospace;
background: #330080;
color: #e5e5e5;
display: flex;
flex-direction: column;
align-items: center;
}
header {
display: flex;
flex-direction: column;
align-items: center;
}
main {
width: 40rem;
}
textarea {
width: 100%;
resize: vertical;
}
#website-message {
background: purple;
padding: 0.5rem;
}

63
templates/index.html Normal file
View file

@ -0,0 +1,63 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>gaybook</title>
<link rel="stylesheet" href="/static/style.css">
<script src="/static/proof-of-work.js" defer></script>
</head>
<body>
<header>
<h1>The GayBook&trade;</h1>
<p>If you pass by, feel free to leave a message here!</p>
</header>
<main>
{% if message != "" %}
<section id="website-message">
<h1>Message from the website</h1>
<p>{{ message }}</p>
</section>
{% endif %}
<section>
<h1>Leave a quote!</h1>
<form method="POST">
<label for="username">Username:</label>
<input type="text" name="username" id="username" required>
<br>
<label for="message">Message:</label>
<textarea name="message" id="message" rows="5" required></textarea>
<br>
<label for="proof-of-work">Proof of work:</label>
<input type="text" name="proof-of-work" id="proof-of-work" required>
<button id="generate-pow" style="display: none">
Generate proof of work
</button>
<noscript>
<p>
You have Javascript disabled. You'll need to generate the proof-of-work manually.
<br>
Compute a nonce such that the ascii hex sha1 digest of
"$USERNAME\0$MESSAGE\0$NONCE" starts with 5 zeros. The nonce needs to be a base-10 integer
number encoded in ASCII.
Once you've verified the nonce works, enter it in base10 ascii in the "Proof of work" field.
</p>
</noscript>
<br>
<input type="submit" value="Publish">
</form>
</section>
<section>
<h1>Past quotes</h1>
{% for quote in quotes %}
<p>From: {{ quote.author }}, at {{ quote.timestamp }}</p>
<p>{{ quote.content }}</p>
<hr>
{% endfor %}
</section>
</main>
</body>
</html>