initial 1312 funktioniert ein bisschen
This commit is contained in:
@@ -0,0 +1,190 @@
|
|||||||
|
from flask import render_template, request, make_response
|
||||||
|
import database, re, bcrypt, secrets, time
|
||||||
|
|
||||||
|
from app import app
|
||||||
|
|
||||||
|
def get_session(request):
|
||||||
|
sessionid = request.cookies.get('session')
|
||||||
|
|
||||||
|
if not sessionid:
|
||||||
|
return False
|
||||||
|
|
||||||
|
sessionid = str(sessionid)
|
||||||
|
sessioninfo = database.fetch("""SELECT uid, created_at, expires, description
|
||||||
|
FROM session
|
||||||
|
WHERE value=?;""",
|
||||||
|
[sessionid])
|
||||||
|
|
||||||
|
if len(sessioninfo) == 0:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if len(sessioninfo) > 1:
|
||||||
|
# there is a problem
|
||||||
|
return False
|
||||||
|
|
||||||
|
return sessioninfo[0] + (sessionid,)
|
||||||
|
|
||||||
|
@app.route("/auth/sessioninfo", methods=['GET'])
|
||||||
|
def sessioninfo():
|
||||||
|
time.sleep(secrets.randbelow(100)/50) # Rate limiting
|
||||||
|
|
||||||
|
sessioninfo = get_session(request)
|
||||||
|
|
||||||
|
if not sessioninfo:
|
||||||
|
return "Invalid session"
|
||||||
|
|
||||||
|
(uid, created_at, expires, description) = sessioninfo
|
||||||
|
# uid = sessioninfo[0][0]
|
||||||
|
# created_at = sessioninfo[0][1]
|
||||||
|
# expires = sessioninfo[0][2]
|
||||||
|
# description = sessioninfo[0][3]
|
||||||
|
return render_template("/auth/sessioninfo.html",
|
||||||
|
sessionid=sessionid,
|
||||||
|
uid=uid,
|
||||||
|
created_at=created_at,
|
||||||
|
expires=expires,
|
||||||
|
description=description)
|
||||||
|
|
||||||
|
@app.route("/auth/logout", methods=['GET'])
|
||||||
|
def logout():
|
||||||
|
sessioninfo = get_session(request)
|
||||||
|
|
||||||
|
response = make_response("Erfolgreich abgemeldet")
|
||||||
|
response.set_cookie('session', value='', expires=0)
|
||||||
|
|
||||||
|
if not sessioninfo:
|
||||||
|
return response
|
||||||
|
|
||||||
|
database.execute("DELETE FROM session WHERE value = ?", [sessioninfo[4]])
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
@app.route("/auth/login", methods=['GET', 'POST'])
|
||||||
|
def login():
|
||||||
|
sessioninfo = get_session(request)
|
||||||
|
|
||||||
|
if sessioninfo:
|
||||||
|
uid = sessioninfo[0]
|
||||||
|
username = database.fetch("SELECT username FROM user WHERE uid = ?", [uid])
|
||||||
|
username = username[0][0]
|
||||||
|
return render_template("auth/loginsuccess.html", username=username)
|
||||||
|
|
||||||
|
|
||||||
|
if request.method == 'GET':
|
||||||
|
return render_template("auth/login.html",
|
||||||
|
response="",
|
||||||
|
username="")
|
||||||
|
|
||||||
|
username = _sanitizeUsername(request.form['user'])
|
||||||
|
if len(username) < 3:
|
||||||
|
return render_template("auth/login.html",
|
||||||
|
response=f"Der Nutzername {username} ist zu kurz.",
|
||||||
|
username=username)
|
||||||
|
|
||||||
|
userquery = database.fetch("SELECT uid, password FROM user WHERE username=?", [username])
|
||||||
|
|
||||||
|
# STOP TIMING ATTACKS
|
||||||
|
time.sleep(secrets.randbelow(100)/50)
|
||||||
|
|
||||||
|
if len(userquery) == 0:
|
||||||
|
return render_template("auth/login.html",
|
||||||
|
response=f"Der Nutzername >{username} oder das Passwort ist falsch.",
|
||||||
|
username=username)
|
||||||
|
|
||||||
|
uidquery = userquery[0][0]
|
||||||
|
passquery = userquery[0][1]
|
||||||
|
|
||||||
|
passform = str(request.form['pass'])
|
||||||
|
passform = passform.encode("utf8")
|
||||||
|
passcorrect = bcrypt.checkpw(passform, passquery)
|
||||||
|
|
||||||
|
if not passcorrect:
|
||||||
|
return render_template("auth/login.html",
|
||||||
|
response=f"Der Nutzername {username} oder das >Passwort ist falsch.",
|
||||||
|
username=username)
|
||||||
|
|
||||||
|
now = int(time.time())
|
||||||
|
|
||||||
|
timeout = int(request.form['timeout'])
|
||||||
|
timeout = now + timeout
|
||||||
|
|
||||||
|
session = secrets.randbelow(999999999)
|
||||||
|
session = str(session) + str(now)
|
||||||
|
|
||||||
|
database.execute("DELETE FROM session WHERE expires < ?", [str(now)])
|
||||||
|
database.execute("""INSERT INTO session
|
||||||
|
(uid, created_at, expires, description, value)
|
||||||
|
VALUES (?, ?, ?, ?, ?);""",
|
||||||
|
(uidquery, int(time.time()), timeout, "", session)
|
||||||
|
)
|
||||||
|
|
||||||
|
response = make_response(render_template("auth/loginsuccess.html", username=username))
|
||||||
|
response.set_cookie('session', value=session, expires=timeout)
|
||||||
|
return response
|
||||||
|
|
||||||
|
@app.route("/auth/newuser", methods=['GET', 'POST'])
|
||||||
|
def newuser():
|
||||||
|
if request.method == 'GET':
|
||||||
|
return render_template("auth/register.html",
|
||||||
|
responses=[],
|
||||||
|
username="")
|
||||||
|
|
||||||
|
success = True
|
||||||
|
responses = []
|
||||||
|
|
||||||
|
username = _sanitizeUsername(request.form['user'])
|
||||||
|
if len(username) < 3:
|
||||||
|
success = False
|
||||||
|
responses.append(f"Der Nutzername {username} ist zu kurz.")
|
||||||
|
usernamequery = database.fetch("SELECT id FROM user WHERE username=?", [username])
|
||||||
|
if len(usernamequery) != 0:
|
||||||
|
success = False
|
||||||
|
responses.append(f"Der Nutzername {username} wird bereits verwendet.")
|
||||||
|
|
||||||
|
pass1 = str(request.form['pass1'])
|
||||||
|
pass2 = str(request.form['pass2'])
|
||||||
|
if pass1 != pass2:
|
||||||
|
success = False
|
||||||
|
responses.append(f"Passwort und Passwortbestätigung sind ungleich.")
|
||||||
|
|
||||||
|
if len(pass1) < 10:
|
||||||
|
success = False
|
||||||
|
responses.append(f"Das Passwort ist kürzer als 10 Zeichen.")
|
||||||
|
|
||||||
|
if not success:
|
||||||
|
return render_template("auth/register.html",
|
||||||
|
responses=responses,
|
||||||
|
username=username)
|
||||||
|
|
||||||
|
uid = secrets.randbelow(99999999)
|
||||||
|
created_at = int(time.time())
|
||||||
|
# emailcode = secrets.randbelow(999999)
|
||||||
|
passsalt = bcrypt.gensalt()
|
||||||
|
passhash = bcrypt.hashpw(pass1.encode("utf8"), passsalt)
|
||||||
|
|
||||||
|
database.execute("""INSERT INTO user
|
||||||
|
(uid, created_at, username, password) VALUES
|
||||||
|
(?, ?, ?, ?)""",
|
||||||
|
(uid, created_at, username, passhash))
|
||||||
|
|
||||||
|
return render_template("auth/registersuccess.html", username=username)
|
||||||
|
|
||||||
|
def _sanitizeUsername(username):
|
||||||
|
username = str(username)
|
||||||
|
username = username.strip()
|
||||||
|
username = username[:32]
|
||||||
|
|
||||||
|
# Remove all non-word characters (everything except numbers and letters)
|
||||||
|
username = re.sub(r"[^._\w\s-]", '', username)
|
||||||
|
|
||||||
|
# Replace all runs of whitespace with a single dash
|
||||||
|
username = re.sub(r"\s+", '_', username)
|
||||||
|
|
||||||
|
return username
|
||||||
|
|
||||||
|
def _verifyEmail(email):
|
||||||
|
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
|
||||||
|
if re.match(pattern, email):
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
+21
@@ -0,0 +1,21 @@
|
|||||||
|
import sqlite3
|
||||||
|
|
||||||
|
def execute(query, data):
|
||||||
|
connection = sqlite3.connect("kver.db")
|
||||||
|
cursor = connection.cursor()
|
||||||
|
|
||||||
|
cursor.execute(query, data)
|
||||||
|
|
||||||
|
connection.commit()
|
||||||
|
connection.close()
|
||||||
|
|
||||||
|
def fetch(query, data):
|
||||||
|
connection = sqlite3.connect("kver.db")
|
||||||
|
cursor = connection.cursor()
|
||||||
|
|
||||||
|
result = cursor.execute(query, data)
|
||||||
|
result = result.fetchall()
|
||||||
|
|
||||||
|
connection.close()
|
||||||
|
|
||||||
|
return result
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import sqlite3
|
||||||
|
from flask import render_template
|
||||||
|
from __main__ import app
|
||||||
|
|
||||||
|
@app.route("/database")
|
||||||
|
def database():
|
||||||
|
con = sqlite3.connect("kver.db")
|
||||||
|
cursor = con.cursor()
|
||||||
|
|
||||||
|
# cursor.execute("INSERT INTO movie VALUES (?, ?, ?)", ("Erster Film", 1900, 8.2))
|
||||||
|
# con.commit()
|
||||||
|
|
||||||
|
res = cursor.execute("SELECT name FROM sqlite_master WHERE type='table';")
|
||||||
|
|
||||||
|
return render_template("database.html", tables=res.fetchall())
|
||||||
|
|
||||||
|
# !!! USER VALUE IN SQL STATEMENT !!!
|
||||||
|
@app.route("/database/<table>")
|
||||||
|
def db_table(table):
|
||||||
|
|
||||||
|
con = sqlite3.connect("kver.db")
|
||||||
|
cursor = con.cursor()
|
||||||
|
|
||||||
|
# cursor.execute("INSERT INTO movie VALUES (?, ?, ?)", ("Erster Film", 1900, 8.2))
|
||||||
|
# con.commit()
|
||||||
|
|
||||||
|
# columns = cursor.execute("SELECT sql FROM sqlite_master WHERE type='table' AND name=?;", [table])
|
||||||
|
# !!! USER VALUE IN SQL-STATEMENT !!!
|
||||||
|
data = cursor.execute(f"SELECT * FROM {table};")
|
||||||
|
|
||||||
|
return render_template("database_table.html", data=data.fetchall())
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
from flask import render_template, request
|
||||||
|
from PIL import Image
|
||||||
|
# import database, re, bcrypt, secrets, time
|
||||||
|
import secrets, time, os, database
|
||||||
|
from auth import get_session
|
||||||
|
from app import app
|
||||||
|
|
||||||
|
@app.route("/entry/feed/<page>", methods=['GET'])
|
||||||
|
def feed(page):
|
||||||
|
page = int(page, 0)
|
||||||
|
page = max(0,page)
|
||||||
|
page = min(100,page)
|
||||||
|
|
||||||
|
count = 20
|
||||||
|
offset = page*count
|
||||||
|
|
||||||
|
posts = database.fetch("SELECT * FROM entry ORDER BY created_at DESC LIMIT ? OFFSET ?", [count, offset])
|
||||||
|
|
||||||
|
posts_with_usernames = []
|
||||||
|
for post in posts:
|
||||||
|
username = database.fetch("SELECT username FROM user WHERE uid = ?", [post[2]])
|
||||||
|
posts_with_usernames.append(post + username[0])
|
||||||
|
|
||||||
|
return render_template("entry/feed.html", posts=posts_with_usernames)
|
||||||
|
|
||||||
|
@app.route("/entry/create", methods=['GET', 'POST'])
|
||||||
|
def newentry():
|
||||||
|
session = get_session(request)
|
||||||
|
|
||||||
|
if not session:
|
||||||
|
return "Melde dich an, um einen Beitrag zu erstellen!"
|
||||||
|
|
||||||
|
uid = session[0]
|
||||||
|
|
||||||
|
if request.method == 'GET':
|
||||||
|
return render_template('entry/create.html')
|
||||||
|
|
||||||
|
content = str(request.form["content"])
|
||||||
|
content = content[:1000]
|
||||||
|
|
||||||
|
fileattached = False
|
||||||
|
if 'file' in request.files:
|
||||||
|
file = request.files['file']
|
||||||
|
fileattached=True
|
||||||
|
|
||||||
|
if fileattached:
|
||||||
|
fileattached = _storeimage(file)
|
||||||
|
|
||||||
|
if not fileattached and content == '':
|
||||||
|
return render_template('entry/create.html', responses=["Leerer Beitrag!"])
|
||||||
|
|
||||||
|
if not fileattached:
|
||||||
|
fileattached = "none"
|
||||||
|
|
||||||
|
pid = secrets.randbelow(999999999999)
|
||||||
|
now = int(time.time())
|
||||||
|
database.execute("""INSERT INTO entry
|
||||||
|
(pid, uid, created_at, content, filepath)
|
||||||
|
VALUES (?, ?, ?, ?, ?)""",
|
||||||
|
(pid, uid, now, content, fileattached))
|
||||||
|
|
||||||
|
return render_template('entry/create.html', responses=[content, str(fileattached)])
|
||||||
|
|
||||||
|
def _storeimage(file):
|
||||||
|
maxsize = 1024
|
||||||
|
tmpfile = secrets.randbelow(999999)
|
||||||
|
tmppath = f"tmp/{tmpfile}"
|
||||||
|
|
||||||
|
import resizegif
|
||||||
|
|
||||||
|
try:
|
||||||
|
file.save(tmppath)
|
||||||
|
|
||||||
|
i = Image.open(tmppath)
|
||||||
|
if i.format == 'GIF':
|
||||||
|
filetype = 'gif'
|
||||||
|
else:
|
||||||
|
i = i.convert("RGB")
|
||||||
|
filetype = 'jpg'
|
||||||
|
|
||||||
|
newpath = f"static/media/{int(time.time())}-{tmpfile}.{filetype}"
|
||||||
|
|
||||||
|
(width, height) = i.size;
|
||||||
|
aspect = width/height
|
||||||
|
|
||||||
|
if width < maxsize and height < maxsize:
|
||||||
|
if filetype == 'gif':
|
||||||
|
resizegif.resize_gif(i, newpath, (width, height))
|
||||||
|
else:
|
||||||
|
i.save(newpath)
|
||||||
|
|
||||||
|
os.remove(tmppath)
|
||||||
|
return newpath
|
||||||
|
|
||||||
|
if width > height:
|
||||||
|
width = maxsize
|
||||||
|
height = maxsize/aspect
|
||||||
|
else:
|
||||||
|
height = maxsize
|
||||||
|
width = aspect*maxsize
|
||||||
|
|
||||||
|
if filetype == 'gif':
|
||||||
|
resizegif.resize_gif(i, newpath, (width, height))
|
||||||
|
else:
|
||||||
|
i.thumbnail((width, height), Image.Resampling.LANCZOS)
|
||||||
|
i.save(newpath)
|
||||||
|
|
||||||
|
os.remove(tmppath)
|
||||||
|
return newpath
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
os.remove(tmppath)
|
||||||
|
return False
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import os
|
||||||
|
from PIL import Image
|
||||||
|
from flask import request, render_template
|
||||||
|
from __main__ import app
|
||||||
|
|
||||||
|
@app.route("/upload", methods=['GET', 'POST'])
|
||||||
|
def upload():
|
||||||
|
state="Nothing done..."
|
||||||
|
success=False
|
||||||
|
|
||||||
|
if request.method == 'POST':
|
||||||
|
if 'file' not in request.files:
|
||||||
|
state='No file'
|
||||||
|
|
||||||
|
file = request.files['file']
|
||||||
|
|
||||||
|
if file.filename == '':
|
||||||
|
state="No File selected!"
|
||||||
|
|
||||||
|
if file:
|
||||||
|
filename = "uploadedfile"
|
||||||
|
file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
|
||||||
|
state="File uploaded!"
|
||||||
|
success=True
|
||||||
|
|
||||||
|
imagedata = []
|
||||||
|
if success:
|
||||||
|
try:
|
||||||
|
i = Image.open("uploads/uploadedfile")
|
||||||
|
imagedata.append(i.format)
|
||||||
|
imagedata.append(i.size)
|
||||||
|
except Exception as e:
|
||||||
|
imagedata.append(e)
|
||||||
|
|
||||||
|
|
||||||
|
return render_template("upload.html", state=state, imagedata=imagedata)
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import os
|
||||||
|
from flask import Flask, request, render_template
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from werkzeug.exceptions import HTTPException
|
||||||
|
|
||||||
|
from app import app
|
||||||
|
app.config['UPLOAD_FOLDER'] = "uploads/"
|
||||||
|
|
||||||
|
@app.errorhandler(400)
|
||||||
|
def handle_bad_request(e):
|
||||||
|
print(f"400 BAD REQUEST: {e}")
|
||||||
|
return '400<br>BAD REQUEST'
|
||||||
|
|
||||||
|
@app.template_filter('timestamp')
|
||||||
|
def timestamp_filter(value):
|
||||||
|
try:
|
||||||
|
dt = datetime.fromtimestamp(value)
|
||||||
|
return dt.strftime("%d.%m.%Y %H:%M")
|
||||||
|
except:
|
||||||
|
return value
|
||||||
|
|
||||||
|
# import sendmail
|
||||||
|
import auth
|
||||||
|
# import dbcontroll
|
||||||
|
# import imageupload
|
||||||
|
import entry
|
||||||
|
|
||||||
|
@app.route("/header")
|
||||||
|
def header():
|
||||||
|
return render_template("header.html")
|
||||||
|
|
||||||
|
@app.route("/")
|
||||||
|
def template():
|
||||||
|
return render_template("index.html", name="Hmmm")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app.run(debug=True)
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
# Source - https://stackoverflow.com/a
|
||||||
|
# Posted by brvoisin
|
||||||
|
# Retrieved 2025-12-13, License - CC BY-SA 4.0
|
||||||
|
|
||||||
|
"""
|
||||||
|
# Resize an animated GIF
|
||||||
|
Inspired from https://gist.github.com/skywodd/8b68bd9c7af048afcedcea3fb1807966
|
||||||
|
Useful links:
|
||||||
|
* https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html#saving
|
||||||
|
* https://stackoverflow.com/a/69850807
|
||||||
|
Example:
|
||||||
|
```
|
||||||
|
python resize_gif.py input.gif output.gif 400,300
|
||||||
|
```
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from PIL import Image
|
||||||
|
from PIL import ImageSequence
|
||||||
|
|
||||||
|
|
||||||
|
def resize_gif(input_image, output_path, max_size):
|
||||||
|
frames = list(_thumbnail_frames(input_image, max_size))
|
||||||
|
output_image = frames[0]
|
||||||
|
output_image.save(
|
||||||
|
output_path,
|
||||||
|
save_all=True,
|
||||||
|
append_images=frames[1:],
|
||||||
|
disposal=input_image.disposal_method,
|
||||||
|
**input_image.info,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _thumbnail_frames(image, max_size):
|
||||||
|
for frame in ImageSequence.Iterator(image):
|
||||||
|
new_frame = frame.copy()
|
||||||
|
new_frame.thumbnail(max_size, Image.Resampling.LANCZOS)
|
||||||
|
yield new_frame
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
max_size = [int(px) for px in sys.argv[3].split(",")] # "150,100" -> (150, 100)
|
||||||
|
resize_gif(sys.argv[1], sys.argv[2], max_size)
|
||||||
+41
@@ -0,0 +1,41 @@
|
|||||||
|
import smtplib, ssl, database, time
|
||||||
|
|
||||||
|
def send(subject, message, recipient, type):
|
||||||
|
hour_ago = int(time.time()) - 3600
|
||||||
|
count = database.fetch("SELECT COUNT(*) FROM email WHERE time > ?", [hour_ago])
|
||||||
|
if count[0][0] > 5:
|
||||||
|
return False
|
||||||
|
|
||||||
|
database.execute("INSERT INTO email (recipient, time, type) VALUES (?, ?, ?)", [
|
||||||
|
recipient,
|
||||||
|
int(time.time()),
|
||||||
|
type
|
||||||
|
])
|
||||||
|
|
||||||
|
smtp_server = "webspace29.do.de"
|
||||||
|
port = 587 # For starttls
|
||||||
|
sender_email = "system@bnd.wtf"
|
||||||
|
password = ""
|
||||||
|
message = f"""From: system@bnd.wtf\nSubject: {subject}
|
||||||
|
|
||||||
|
{message}"""
|
||||||
|
|
||||||
|
# Create a secure SSL context
|
||||||
|
context = ssl.create_default_context()
|
||||||
|
|
||||||
|
# Try to log in to server and send email
|
||||||
|
try:
|
||||||
|
server = smtplib.SMTP(smtp_server,port)
|
||||||
|
server.ehlo() # Can be omitted
|
||||||
|
server.starttls(context=context) # Secure the connection
|
||||||
|
server.ehlo() # Can be omitted
|
||||||
|
server.login(sender_email, password)
|
||||||
|
server.sendmail(sender_email, recipient, message)
|
||||||
|
# TODO: Send email here
|
||||||
|
except Exception as e:
|
||||||
|
# Print any error messages to stdout
|
||||||
|
print(e)
|
||||||
|
finally:
|
||||||
|
server.quit()
|
||||||
|
|
||||||
|
return True
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 119 KiB |
@@ -0,0 +1,110 @@
|
|||||||
|
html {
|
||||||
|
border: 1px solig gray;
|
||||||
|
font-family: "DejaVu Serif"; }
|
||||||
|
|
||||||
|
div.entry {
|
||||||
|
border: 1px solid gray;
|
||||||
|
padding: 10px 15px;
|
||||||
|
margin: 0px; }
|
||||||
|
div.entry div.entrymeta {
|
||||||
|
display: flex;
|
||||||
|
justify-content: left;
|
||||||
|
align-items: center;
|
||||||
|
gap: 15px;
|
||||||
|
cursor: pointer; }
|
||||||
|
div.entry div.entrymeta span.time {
|
||||||
|
color: gray; }
|
||||||
|
div.entry div.entrymeta span.user {
|
||||||
|
text-decoration: underline; }
|
||||||
|
div.entry div.entrymeta img.userimage {
|
||||||
|
width: 80px; }
|
||||||
|
div.entry div.contents p {
|
||||||
|
margin: 8pt 0pt; }
|
||||||
|
div.entry img {
|
||||||
|
max-width: 100%; }
|
||||||
|
div.entry.comment {
|
||||||
|
border: none;
|
||||||
|
padding: 0px 15px;
|
||||||
|
margin: 0px;
|
||||||
|
display: flex;
|
||||||
|
gap: 5px; }
|
||||||
|
div.entry.comment .left {
|
||||||
|
width: 70px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column; }
|
||||||
|
div.entry.comment .left .spacer {
|
||||||
|
background-color: black;
|
||||||
|
height: 100%;
|
||||||
|
width: 6%;
|
||||||
|
margin: 0px 47%; }
|
||||||
|
|
||||||
|
form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 400px;
|
||||||
|
max-width: 100%; }
|
||||||
|
form textarea {
|
||||||
|
resize: vertical;
|
||||||
|
font-size: 1.0rem; }
|
||||||
|
form.create {
|
||||||
|
border: 1px solid gray;
|
||||||
|
padding: 10px 15px;
|
||||||
|
margin: 0px; }
|
||||||
|
form hr {
|
||||||
|
width: 100%; }
|
||||||
|
|
||||||
|
input,
|
||||||
|
textarea,
|
||||||
|
button,
|
||||||
|
select {
|
||||||
|
padding: 4pt 6pt;
|
||||||
|
margin: 3pt 0pt;
|
||||||
|
border: 2px solid black;
|
||||||
|
cursor: pointer;
|
||||||
|
box-shadow: 0px 0px; }
|
||||||
|
input:hover,
|
||||||
|
textarea:hover,
|
||||||
|
button:hover,
|
||||||
|
select:hover {
|
||||||
|
box-shadow: 1px 1px; }
|
||||||
|
input:focus,
|
||||||
|
textarea:focus,
|
||||||
|
button:focus,
|
||||||
|
select:focus {
|
||||||
|
box-shadow: 3px 3px black;
|
||||||
|
outline: none; }
|
||||||
|
|
||||||
|
button {
|
||||||
|
background-color: #ffabcd; }
|
||||||
|
|
||||||
|
#response div {
|
||||||
|
padding: 4pt 6pt; }
|
||||||
|
|
||||||
|
#response .error {
|
||||||
|
color: red; }
|
||||||
|
|
||||||
|
.loading {
|
||||||
|
background-color: #fffa;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-content: center;
|
||||||
|
transition: background-color 5s;
|
||||||
|
transition-timing-function: ease-in; }
|
||||||
|
|
||||||
|
.post {
|
||||||
|
border: 1px solid black;
|
||||||
|
padding: 15px 15px;
|
||||||
|
margin: 10px 0px; }
|
||||||
|
.post img {
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 800px; }
|
||||||
|
.post .meta {
|
||||||
|
color: gray; }
|
||||||
|
.post .content {
|
||||||
|
margin: 10px 0px; }
|
||||||
@@ -0,0 +1,178 @@
|
|||||||
|
// @font-face {
|
||||||
|
// font-family: 'avenir';
|
||||||
|
// src: url('/static/avenir-font/AvenirLTStd-Medium.otf');
|
||||||
|
// font-weight: normal;
|
||||||
|
// font-style: normal;
|
||||||
|
// }
|
||||||
|
|
||||||
|
html {
|
||||||
|
|
||||||
|
// font-family: "avenir";
|
||||||
|
// font-weight: 400;
|
||||||
|
// font-style: normal;
|
||||||
|
|
||||||
|
// background-color: #e9e9ed;
|
||||||
|
border: 1px solig gray;
|
||||||
|
// border-radius: 5px;
|
||||||
|
font-family: "DejaVu Serif";
|
||||||
|
}
|
||||||
|
|
||||||
|
div.entry {
|
||||||
|
// background-color: green;
|
||||||
|
border: 1px solid gray;
|
||||||
|
padding: 10px 15px;
|
||||||
|
margin: 0px;
|
||||||
|
|
||||||
|
div.entrymeta {
|
||||||
|
display: flex;
|
||||||
|
justify-content: left;
|
||||||
|
align-items: center;
|
||||||
|
gap: 15px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
span.time {
|
||||||
|
// background-color: red;
|
||||||
|
color: gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.user {
|
||||||
|
// background-color: blue;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
img.userimage {
|
||||||
|
width: 80px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
div.contents {
|
||||||
|
// background-color: yellow;
|
||||||
|
|
||||||
|
p {
|
||||||
|
// background-color: orange;
|
||||||
|
margin: 8pt 0pt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.comment {
|
||||||
|
border: none;
|
||||||
|
padding: 0px 15px;
|
||||||
|
margin: 0px;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
gap: 5px;
|
||||||
|
|
||||||
|
.left {
|
||||||
|
width: 70px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
// background-color: green;
|
||||||
|
|
||||||
|
.spacer {
|
||||||
|
background-color: black;
|
||||||
|
height: 100%;
|
||||||
|
width: 6%;
|
||||||
|
margin: 0px 47%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// .right {
|
||||||
|
// background-color: red;
|
||||||
|
// }
|
||||||
|
// .contents {
|
||||||
|
// display: flex;
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 400px;
|
||||||
|
max-width: 100%;
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
// width: 100%;
|
||||||
|
resize: vertical;
|
||||||
|
font-size: 1.0rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.create {
|
||||||
|
border: 1px solid gray;
|
||||||
|
padding: 10px 15px;
|
||||||
|
margin: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
hr {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input,
|
||||||
|
textarea,
|
||||||
|
button,
|
||||||
|
select {
|
||||||
|
padding: 4pt 6pt;
|
||||||
|
margin: 3pt 0pt;
|
||||||
|
border: 2px solid black;
|
||||||
|
cursor: pointer;
|
||||||
|
box-shadow: 0px 0px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
box-shadow: 1px 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
box-shadow: 3px 3px black;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
background-color: #ffabcd;
|
||||||
|
}
|
||||||
|
|
||||||
|
#response {
|
||||||
|
div {
|
||||||
|
padding: 4pt 6pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading {
|
||||||
|
// background-image: url('/static/spinner.gif');
|
||||||
|
background-color: #fffa;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-content: center;
|
||||||
|
transition: background-color 5s;
|
||||||
|
transition-timing-function: ease-in;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post {
|
||||||
|
border: 1px solid black;
|
||||||
|
padding: 15px 15px;
|
||||||
|
margin: 10px 0px;
|
||||||
|
img {
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 800px;
|
||||||
|
}
|
||||||
|
.meta {
|
||||||
|
color: gray;
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
margin: 10px 0px;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
$(() => {
|
||||||
|
$('#auth-visible').hide()
|
||||||
|
})
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 7.1 KiB |
@@ -0,0 +1,17 @@
|
|||||||
|
<h2>Einloggen</h2>
|
||||||
|
<form hx-post="/auth/login" hx-target="#auth-rendertarget" hx-indicator=".loading">
|
||||||
|
<input type="text" name="user" placeholder="Nutzername" value="{{ username }}"><br>
|
||||||
|
<input type="password" name="pass" placeholder="Passphrase"><br>
|
||||||
|
<label>Automatisch abmelden in:</label>
|
||||||
|
<select name="timeout">
|
||||||
|
<option value="1200">20 Minuten</option>
|
||||||
|
<option value="3600" selected="selected">1 Stunde</option>
|
||||||
|
<option value="18000">5 Stunden</option>
|
||||||
|
<option value="86400">24 Stunden</option>
|
||||||
|
<option value="31536000">1 Jahr</option>
|
||||||
|
</select>
|
||||||
|
<button type="submit">Anmelden</button>
|
||||||
|
<div>
|
||||||
|
{{response}}
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<h1>Willkommen, {{username}}!</h1>
|
||||||
|
|
||||||
|
<button hx-get="/auth/logout" hx-swap="outerHTML">Abmelden...</button>
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<h2>Neuen Account registrieren</h2>
|
||||||
|
<form hx-post="/auth/newuser" hx-target="#auth-rendertarget" method="post">
|
||||||
|
<input type="text" name="user" placeholder="Nutzername" value="{{ username }}"><br>
|
||||||
|
<input type="password" name="pass1" placeholder="Passphrase"><br>
|
||||||
|
<input type="password" name="pass2" placeholder="Passphrase wiederholen"><br>
|
||||||
|
<label><input type="checkbox" name="pass2"> Ich bin mit der <a href="">Datenschutzerklärung</a> einverstanden!</label>
|
||||||
|
<button type="submit">Account registrieren!</button>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
{% for r in responses %}
|
||||||
|
{{ r }} <br>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<h2>Willkommen im Kontrollverlust!</h2>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
Sessionid: {{ sessionid }} <br>
|
||||||
|
UID: {{ uid }} <br>
|
||||||
|
Created: {{ created_at | timestamp }} <br>
|
||||||
|
Expires: {{ expires | timestamp }} <br>
|
||||||
|
Description: {{ description }} <br>
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||||
|
<title>{% block title %}{% endblock %}</title>
|
||||||
|
<link rel="stylesheet" href="/static/css/main.css">
|
||||||
|
<script
|
||||||
|
src="https://cdn.jsdelivr.net/npm/htmx.org@2.0.8/dist/htmx.min.js"
|
||||||
|
integrity="sha384-/TgkGk7p307TH7EXJDuUlgG3Ce1UVolAOFopFekQkkXihi5u/6OCvVKyz1W+idaz"
|
||||||
|
crossorigin="anonymous">
|
||||||
|
</script>
|
||||||
|
<script
|
||||||
|
src="https://code.jquery.com/jquery-3.7.1.min.js"
|
||||||
|
integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo="
|
||||||
|
crossorigin="anonymous">
|
||||||
|
</script>
|
||||||
|
<script src="/static/js/main.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<a href="/docs/impressum.html">Impressum</a>
|
||||||
|
<a href="/docs/datenschutz.html">Datenschutz</a>
|
||||||
|
<a href="/docs/kontakt.html">Kontakt</a>
|
||||||
|
<div class="htmx-indicator loading"></div>
|
||||||
|
<h1>Kontrollverlust</h1>
|
||||||
|
<span>Verbuggte scheise hier. Komm hack mich doch, trau dich!</span>
|
||||||
|
<div hx-get="/header" hx-trigger="load"></div>
|
||||||
|
{% block content %}{% endblock %}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||||
|
<title>Database</title>
|
||||||
|
<link rel="stylesheet" href="style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{% for tablename in tables %}
|
||||||
|
<a href="/database/{{ tablename[0] }}">{{ tablename[0] }}</a><br>
|
||||||
|
{% endfor %}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<table>
|
||||||
|
{% for row in data %}
|
||||||
|
<tr>
|
||||||
|
{% for column in row %}
|
||||||
|
<td>{{ column }}</td>
|
||||||
|
{% endfor %}
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<form hx-post="/entry/create" hx-target="#entry-rendertarget" method="post" enctype="multipart/form-data">
|
||||||
|
<textarea name="content"></textarea>
|
||||||
|
<input type="file" name="file">
|
||||||
|
<button type="submit">Beitrag erstellen!</button>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
{% for r in responses %}
|
||||||
|
{{ r }} <br>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
{% for p in posts %}
|
||||||
|
|
||||||
|
<div class="post">
|
||||||
|
<div class="meta">
|
||||||
|
{{ p[6] }} - {{ p[3] | timestamp }} <br>
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
|
{{ p[4] }}
|
||||||
|
</div>
|
||||||
|
{% if p[5] != 'none' %}
|
||||||
|
<img src="/{{ p[5] }}">
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endfor %}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<button onclick="$('#auth-visible').show()" hx-get="/auth/login" hx-target="#auth-rendertarget">Anmelden</button>
|
||||||
|
<button onclick="$('#auth-visible').show()" hx-get="/auth/newuser" hx-target="#auth-rendertarget">Account erstellen</button>
|
||||||
|
<div id="auth-visible">
|
||||||
|
<div id="auth-rendertarget"></div>
|
||||||
|
<button onclick="$('#auth-visible').hide()">Abbrechen</button>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
{% extends "baseof.html" %}
|
||||||
|
|
||||||
|
{% block title %}Index{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<div hx-get="/entry/create" hx-trigger="load" id="entry-rendertarget"></div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<div hx-get="/entry/feed/0" hx-trigger="load, every 5s"></div>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||||
|
<title>My new Site!</title>
|
||||||
|
<link rel="stylesheet" href="style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Upload new File</h1>
|
||||||
|
<span>{{ state }}</span>
|
||||||
|
<form method="post" enctype="multipart/form-data">
|
||||||
|
<input type=file name=file>
|
||||||
|
<input type=submit value=Upload>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{% for id in imagedata %}
|
||||||
|
{{ id }}
|
||||||
|
{% endfor %}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 119 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 114 KiB |
Reference in New Issue
Block a user