first commit
This commit is contained in:
commit
b40d5a2ee4
15 changed files with 1161 additions and 0 deletions
4
src/requirements.txt
Normal file
4
src/requirements.txt
Normal file
|
@ -0,0 +1,4 @@
|
|||
flask
|
||||
gunicorn
|
||||
bcrypt
|
||||
Flask-Session
|
100
src/static/style.css
Normal file
100
src/static/style.css
Normal 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%;
|
||||
}
|
||||
}
|
36
src/templates/account.html
Normal file
36
src/templates/account.html
Normal 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
38
src/templates/index.html
Normal 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
36
src/templates/post.html
Normal 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
35
src/templates/view.html
Normal 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
147
src/wsgi.py
Normal 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"))
|
Loading…
Add table
Add a link
Reference in a new issue