first commit

This commit is contained in:
Patrick 2024-11-16 19:22:55 +01:00
commit b40d5a2ee4
15 changed files with 1161 additions and 0 deletions

4
src/requirements.txt Normal file
View file

@ -0,0 +1,4 @@
flask
gunicorn
bcrypt
Flask-Session

100
src/static/style.css Normal file
View file

@ -0,0 +1,100 @@
/*
This file is part of VM-Experiments.
Licensed under the AGPL-3.0-or-later. See LICENSE for details.
*/
* {
margin: 0;
padding: 0;
box-sizing: border-box;
color: white;
font-family: Verdana, Geneva, Tahoma, sans-serif;
}
body {
display: flex;
flex-direction: column;
align-items: center;
background-color: black;
}
header {
margin-top: 20px;
display: flex;
flex-direction: row;
align-items: center;
width: 80%;
justify-content: space-between;
padding-left: 10px;
padding-right: 10px;
}
article {
width: 80%;
margin: 20px;
background-color: rgb(63, 63, 63);
padding: 10px;
border-radius: 10px;
}
.post {
margin-bottom: 20px;
background-color: gray;
padding: 10px;
border-radius: 10px;
}
.post {
overflow: hidden;
text-overflow: ellipsis;
}
.description {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
line-clamp: 3;
overflow: hidden;
text-overflow: ellipsis;
}
.title,
.title_full {
display: inline;
}
#form {
display: flex;
flex-direction: column;
}
input,
textarea {
font-size: medium;
background-color: gray;
border: 0;
border-radius: 5px;
padding: 5px;
margin-top: 5px;
}
#create {
background-color: gray;
padding: 5px;
border-radius: 5px;
}
a {
text-decoration: none;
}
@media (max-width: 640px) {
header {
width: 100%;
}
article {
width: 100%;
}
}

View file

@ -0,0 +1,36 @@
<!--
This file is part of VM-Experiments.
Licensed under the AGPL-3.0-or-later. See LICENSE for details.
-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="/static/style.css">
<title>ThreadSphere</title>
</head>
<body>
<header>
<a href="/">
<h1>ThreadSphere</h1>
</a>
<div>
<a href="/account" id="create">{{ status }}</a>
<a href="/post" id="create">Create Post</a>
</div>
</header>
<article>
<form id="form" action="/log_in" method="post">
<label for="textInput">Create your user:</label>
<input id="title_input" type="text" name="username" required>
<input id="title_input" type="text" name="password" required>
<input type="submit" value="Create Account / Log In">
</form>
</article>
</body>
</html>

38
src/templates/index.html Normal file
View file

@ -0,0 +1,38 @@
<!--
This file is part of VM-Experiments.
Licensed under the AGPL-3.0-or-later. See LICENSE for details.
-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="/static/style.css">
<title>ThreadSphere</title>
</head>
<body>
<header>
<a href="/">
<h1>ThreadSphere</h1>
</a>
<div>
<a href="/account" id="create">{{ status }}</a>
<a href="/post" id="create">Create Post</a>
</div>
</header>
<article>
{% for post_id, title, user_id, description in posts %}
<a href="/view?post={{ post_id }}">
<div class="post">
<h3 class="title">{{ title }}</h3>
<p class="description">{{ description }}</p>
</div>
</a>
{% endfor %}
</article>
</body>
</html>

36
src/templates/post.html Normal file
View file

@ -0,0 +1,36 @@
<!--
This file is part of VM-Experiments.
Licensed under the AGPL-3.0-or-later. See LICENSE for details.
-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="/static/style.css">
<title>ThreadSphere</title>
</head>
<body>
<header>
<a href="/">
<h1>ThreadSphere</h1>
</a>
<div>
<a href="/account" id="create">{{ status }}</a>
<a href="/post" id="create">Create Post</a>
</div>
</header>
<article>
<form id="form" action="/create_post" method="post">
<label for="textInput">Create your post:</label>
<input id="title_input" type="text" name="title" required>
<textarea name="description" rows="4" cols="50" required></textarea>
<input type="submit" value="Create Post">
</form>
</article>
</body>
</html>

35
src/templates/view.html Normal file
View file

@ -0,0 +1,35 @@
<!--
This file is part of VM-Experiments.
Licensed under the AGPL-3.0-or-later. See LICENSE for details.
-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="/static/style.css">
<title>ThreadSphere</title>
</head>
<body>
<header>
<a href="/">
<h1>ThreadSphere</h1>
</a>
<div>
<a href="/account" id="create">{{ status }}</a>
<a href="/post" id="create">Create Post</a>
</div>
</header>
<article>
<div class="post">
<h3 class="title_full">{{ title }}</h3>
<p class="title_full"> by {{ user }}</p>
<p class="description_full">{{ description }}</p>
</div>
</article>
</body>
</html>

147
src/wsgi.py Normal file
View file

@ -0,0 +1,147 @@
# This file is part of VM-Experiments.
# Licensed under the AGPL-3.0-or-later. See LICENSE for details.
from flask import Flask, session, render_template, request, redirect, url_for
import sqlite3
import bcrypt
app = Flask(__name__)
app.secret_key = "your_secret_key"
def sqlite_prep():
connection = sqlite3.connect("posts.db")
cursor = connection.cursor()
cursor.execute("""
CREATE TABLE IF NOT EXISTS posts (
id INTEGER PRIMARY KEY,
title TEXT NOT NULL,
user_id TEXT NOT NULL,
description TEXT NOT NULL
)
""")
cursor.execute("""
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY,
username TEXT NOT NULL,
password TEXT NOT NULL
)
""")
@app.route("/")
def index():
sqlite_prep()
connection = sqlite3.connect("posts.db")
cursor = connection.cursor()
cursor.execute("SELECT * FROM posts")
posts = cursor.fetchall()
if "user_id" not in session:
status = "Log In"
else:
status = "Log Out"
return render_template("index.html", posts=posts, status=status)
@app.route("/post")
def post():
if "user_id" not in session:
return redirect(url_for("account"))
return render_template("post.html", status="Log Out")
@app.route("/account")
def account():
if "user_id" not in session:
return render_template("account.html", status="Log In")
session.pop("user_id", None)
return redirect(url_for("index"))
@app.route("/log_in", methods=["POST"])
def log_in():
username = request.form["username"]
password = request.form["password"]
sqlite_prep()
connection = sqlite3.connect("posts.db")
cursor = connection.cursor()
cursor.execute("SELECT * FROM users")
users = cursor.fetchall()
for user_id, db_username, db_password in users:
if db_username == username:
if bcrypt.checkpw(password.encode("utf-8"), db_password):
session["user_id"] = user_id
return redirect(url_for("index"))
else:
return "<p>WRONG PASSWORD</p>"
salt = bcrypt.gensalt()
hashed_password = bcrypt.hashpw(password.encode("utf-8"), salt)
cursor.execute(
"INSERT INTO users (username, password) VALUES (?, ?)",
(username, hashed_password),
)
connection.commit()
for user_id, db_username, db_password in users:
if db_username == username:
session["user_id"] = user_id
return redirect(url_for("index"))
@app.route("/view", methods=["GET"])
def view():
post = request.args.get("post")
sqlite_prep()
connection = sqlite3.connect("posts.db")
cursor = connection.cursor()
cursor.execute("SELECT * FROM posts")
posts = cursor.fetchall()
for post_id, title, user_id, description in posts:
if post_id == int(post):
if "user_id" not in session:
status = "Log In"
else:
status = "Log Out"
cursor.execute(
"""
SELECT users.username
FROM posts
JOIN users ON posts.user_id = users.id
WHERE posts.id = ?
""",
(post_id,),
)
username = cursor.fetchone()
if not username:
username = ("Unknown",)
return render_template(
"view.html",
post_id=post_id,
user=username[0],
title=title,
description=description,
status=status,
)
if "user_id" not in session:
status = "Log In"
else:
status = "Log Out"
return render_template("view.html", status=status)
@app.route("/create_post", methods=["POST"])
def create_post():
if "user_id" not in session:
return redirect(url_for("account"))
title = request.form["title"]
description = request.form["description"]
sqlite_prep()
connection = sqlite3.connect("posts.db")
cursor = connection.cursor()
cursor.execute(
"INSERT INTO posts (title, user_id, description) VALUES (?, ?, ?)",
(title, session["user_id"], description),
)
connection.commit()
return redirect(url_for("index"))