infopage/_server.py

510 lines
20 KiB
Python
Raw Normal View History

2024-02-20 19:26:24 -05:00
CONTENT_DIRECTORY = "./public/"
SAVING_DIRECTORY = "./save/"
UPGRADE_TO_HTTPS = False
2024-02-20 14:55:22 -05:00
import hashlib
2024-02-26 17:33:35 -05:00
import random
2024-02-20 14:55:22 -05:00
import shutil
import flask
import json
import os
2024-03-19 18:42:28 -04:00
from DotIndex import DotIndex
2024-02-20 14:55:22 -05:00
from typing import Union, Callable
2024-02-20 19:26:24 -05:00
from flask import request, redirect
from werkzeug.middleware.proxy_fix import ProxyFix
2024-02-20 14:55:22 -05:00
app = flask.Flask(__name__)
2024-02-26 17:33:35 -05:00
app.url_map.strict_slashes = False
2024-02-20 14:55:22 -05:00
2024-02-22 19:42:05 -05:00
def validate_color(color: str) -> bool:
if len(color) != 7 or color[0] != "#":
return False
for i in color[1::]:
if i not in "abcdef0123456789":
return False
return True
2024-02-20 14:55:22 -05:00
def sort_list(l: list[list[str]]) -> list[list[str]]:
output = []
for i in l:
if i[0] and i[1] in ["1", "2", "3", "4"]:
output.append(i)
return sorted(output, key=lambda x: {"1": "d", "2": "c", "3": "b", "4": "a"}[x[1]] + x[0])
def return_dynamic_content_type(content: Union[str, bytes], content_type: str="text/html") -> flask.Response:
response = flask.make_response(content)
response.headers["Content-Type"] = content_type
return response
def sha(string: Union[str, bytes]) -> str:
if type(string) == str:
return hashlib.sha256(str.encode(string)).hexdigest()
elif type(string) == bytes:
return hashlib.sha256(string).hexdigest()
return ""
def ensure_file(path: str, *, default_value: str="", folder: bool=False) -> None:
if os.path.exists(path):
if folder and not os.path.isdir(path):
os.remove(path)
os.makedirs(path)
elif not folder and os.path.isdir(path):
shutil.rmtree(path, ignore_errors=True)
f = open(path, "w")
f.write(default_value)
f.close()
else:
if folder:
os.makedirs(path)
else:
f = open(path, "w")
f.write(default_value)
f.close()
def escape_html(string: str) -> str:
2024-02-26 17:33:35 -05:00
return string.replace("&", "&amp;").replace("<", "&lt;").replace("\"", "&quot;")
def generate_token(username: str, passhash: str) -> str:
return sha(sha(f"{username}:{passhash}") + "among us in real life, sus, sus")
def list_public(
sort: str="alphabetical",
page: int=0,
limit: int=25
) -> dict:
# Sort: "alphabetical", "random"
# Page: for "alphabetical", page for next. 0 is first page, 1 is second...
x = json.loads(open(f"{SAVING_DIRECTORY}public/list.json", "r").read())
output = {
"end": True,
"list": []
}
if sort == "alphabetical":
x = x[limit * page::]
output["end"] = len(x) <= limit
for i in x[:limit:]:
q = json.loads(open(f"{SAVING_DIRECTORY}{i}.json", "r").read())
output["list"].append({
"colors": q["colors"],
"display_name": q["display_name"],
"bio": q["description"],
"username": i
})
elif sort == "random":
random.shuffle(x)
for i in x[:limit:]:
q = json.loads(open(f"{SAVING_DIRECTORY}{i}.json", "r").read())
output["list"].append({
"colors": q["colors"],
"display_name": q["display_name"],
"bio": q["description"],
"username": i
})
return output
2024-02-20 14:55:22 -05:00
def create_file_serve(file) -> Callable:
2024-02-26 17:33:35 -05:00
x = lambda property=None: flask.send_file(f"{CONTENT_DIRECTORY}{file}")
2024-02-20 14:55:22 -05:00
x.__name__ = file
return x
def create_folder_serve(directory) -> Callable:
x = lambda file: flask.send_from_directory(f"{CONTENT_DIRECTORY}{directory}", file)
x.__name__ = directory
return x
2024-03-19 18:42:28 -04:00
def get_template(json, username):
def add_to_output(starting, json, key, title):
starting += f'<div class="added" id="{key}"><h2>{title}</h2>'
for i in json[key]:
starting += f"""<div {'class="accent"' if i[1] == '4' else 'style="color: var(--text-low-opacity);"' if i[1] == '1' else ''}>{icons[i[1]]} {escape_html(i[0])}</div>"""
return starting + "</div>"
json = DotIndex(json)
icons = {
"1": '<svg style="fill: var(--text-low-opacity);" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M323.8 477.2c-38.2 10.9-78.1-11.2-89-49.4l-5.7-20c-3.7-13-10.4-25-19.5-35l-51.3-56.4c-8.9-9.8-8.2-25 1.6-33.9s25-8.2 33.9 1.6l51.3 56.4c14.1 15.5 24.4 34 30.1 54.1l5.7 20c3.6 12.7 16.9 20.1 29.7 16.5s20.1-16.9 16.5-29.7l-5.7-20c-5.7-19.9-14.7-38.7-26.6-55.5-5.2-7.3-5.8-16.9-1.7-24.9s12.3-13 21.3-13H448c8.8 0 16-7.2 16-16 0-6.8-4.3-12.7-10.4-15-7.4-2.8-13-9-14.9-16.7s.1-15.8 5.3-21.7c2.5-2.8 4-6.5 4-10.6 0-7.8-5.6-14.3-13-15.7-8.2-1.6-15.1-7.3-18-15.2s-1.6-16.7 3.6-23.3c2.1-2.7 3.4-6.1 3.4-9.9 0-6.7-4.2-12.6-10.2-14.9-11.5-4.5-17.7-16.9-14.4-28.8.4-1.3.6-2.8.6-4.3 0-8.8-7.2-16-16-16h-97.5c-12.6 0-25 3.7-35.5 10.7l-61.7 41.1c-11 7.4-25.9 4.4-33.3-6.7s-4.4-25.9 6.7-33.3l61.7-41.1c18.4-12.3 40-18.8 62.1-18.8H384c34.7 0 62.9 27.6 64 62 14.6 11.7 24 29.7 24 50 0 4.5-.5 8.8-1.3 13 15.4 11.7 25.3 30.2 25.3 51 0 6.5-1 12.8-2.8 18.7 11.6 11.8 18.8 27.8 18.8 45.5 0 35.3-28.6 64-64 64h-92.3c4.7 10.4 8.7 21.2 11.8 32.2l5.7 20c10.9 38.2-11.2 78.1-49.4 89zM32 384c-17.7 0-32-14.3-32-32V128c0-17.7 14.3-32 32-32h64c17.7 0 32 14.3 32 32v224c0 17.7-14.3 32-32 32H32z"/></svg>',
"2": '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M100.5 176c-29 0-52.5 23.5-52.5 52.5V320c0 13.3-10.7 24-24 24S0 333.3 0 320v-91.5C0 173 45 128 100.5 128c29.6 0 57.6 13 76.7 35.6l130.2 153.8c10 11.8 24.6 18.6 40.1 18.6 29 0 52.5-23.5 52.5-52.5V192c0-13.3 10.7-24 24-24s24 10.7 24 24v91.5C448 339 403 384 347.5 384c-29.6 0-57.6-13-76.7-35.6L140.6 194.6c-10-11.8-24.6-18.6-40.1-18.6z"/></svg>',
"3": '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M323.8 34.8c-38.2-10.9-78.1 11.2-89 49.4l-5.7 20c-3.7 13-10.4 25-19.5 35l-51.3 56.4c-8.9 9.8-8.2 25 1.6 33.9s25 8.2 33.9-1.6l51.3-56.4c14.1-15.5 24.4-34 30.1-54.1l5.7-20c3.6-12.7 16.9-20.1 29.7-16.5s20.1 16.9 16.5 29.7l-5.7 20c-5.7 19.9-14.7 38.7-26.6 55.5-5.2 7.3-5.8 16.9-1.7 24.9s12.3 13 21.3 13H448c8.8 0 16 7.2 16 16 0 6.8-4.3 12.7-10.4 15-7.4 2.8-13 9-14.9 16.7s.1 15.8 5.3 21.7c2.5 2.8 4 6.5 4 10.6 0 7.8-5.6 14.3-13 15.7-8.2 1.6-15.1 7.3-18 15.2s-1.6 16.7 3.6 23.3c2.1 2.7 3.4 6.1 3.4 9.9 0 6.7-4.2 12.6-10.2 14.9-11.5 4.5-17.7 16.9-14.4 28.8.4 1.3.6 2.8.6 4.3 0 8.8-7.2 16-16 16h-97.5c-12.6 0-25-3.7-35.5-10.7l-61.7-41.1c-11-7.4-25.9-4.4-33.3 6.7s-4.4 25.9 6.7 33.3l61.7 41.1c18.4 12.3 40 18.8 62.1 18.8H384c34.7 0 62.9-27.6 64-62 14.6-11.7 24-29.7 24-50 0-4.5-.5-8.8-1.3-13 15.4-11.7 25.3-30.2 25.3-51 0-6.5-1-12.8-2.8-18.7 11.6-11.8 18.8-27.8 18.8-45.5 0-35.3-28.6-64-64-64h-92.3c4.7-10.4 8.7-21.2 11.8-32.2l5.7-20c10.9-38.2-11.2-78.1-49.4-89zM32 192c-17.7 0-32 14.3-32 32v224c0 17.7 14.3 32 32 32h64c17.7 0 32-14.3 32-32V224c0-17.7-14.3-32-32-32H32z"/></svg>',
"4": '<svg class="accent" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="m47.6 300.4 180.7 168.7c7.5 7 17.4 10.9 27.7 10.9s20.2-3.9 27.7-10.9l180.7-168.7c30.4-28.3 47.6-68 47.6-109.5v-5.8c0-69.9-50.5-129.5-119.4-141-45.6-7.6-92 7.3-124.6 39.9l-12 12-1-12c-32.6-32.6-79-47.5-124.6-39.9C50.5 55.6 0 115.2 0 185.1v5.8c0 41.5 17.2 81.2 47.6 109.5z"/></svg>'
}
styles = f"--background: {json.colors.background}; --background-low-opacity: {json.colors.background}33; --accent: {json.colors.accent}; --accent-low-opacity: {json.colors.accent}66; --text: {json.colors.text}; --text-low-opacity: {json.colors.text}88;" # type: ignore
title = f"{escape_html(json.display_name)} (@{username})".replace("{{TEMPLATE}}", "HA" * 50) # type: ignore
embed = f'<meta name="description" content="{json.display_name} on InfoPage"><meta name="author" content="trinkey"><meta property="og:type" content="website"><meta property="og:title" content="{json.display_name} on InfoPage"><meta property="og:description" content="{json.description}"><meta property="og:url" content="https://infopg.web.app/"><meta property="og:site_name" content="infopg.web.app"><meta name="twitter:card" content="summary_large_image"><meta name="twitter:title" content="{json.display_name} on InfoPage"><meta name="twitter:description" content="{json.description}">' # type: ignore
inner = f'<div id="container"><h1 id="name">{escape_html(json.display_name)}</h1><div id="description">{escape_html(json.description)}</div><div id="word-container">' # type: ignore
inner = add_to_output(inner, json, "names", "Names");
inner = add_to_output(inner, json, "pronouns", "Pronouns");
inner = add_to_output(inner, json, "honorifics", "Honorifics");
inner = add_to_output(inner, json, "compliments", "Compliments");
inner = add_to_output(inner, json, "relationship", "Relationship<br>Descriptions");
inner += "</div>"
return title, inner, styles, embed
def get_user_page(user):
x = open(f"{CONTENT_DIRECTORY}user.html", "r").read()
try:
user_json = json.loads(open(f"{SAVING_DIRECTORY}{user}.json", "r").read())
except FileNotFoundError:
return x.replace("{{TEMPLATE}}", '<h1>User not found!</h1><a href="/signup">Sign up</a> - <a href="/login">Log in</a>').replace("{{TITLE}}", "User not found - InfoPage")
title, inner, styles, embed = get_template(user_json, user)
return x.replace("<body", f'<body style="{styles}"').replace("{{TITLE}}", title).replace("<title", embed + "<title", 1).replace("{{TEMPLATE}}", inner).replace("HA" * 50, "{{TEMPLATE}}")
2024-02-20 14:55:22 -05:00
def api_account_login():
try:
x = json.loads(request.data)
username = x["username"].replace(" ", "").lower()
passhash = x["password"]
except json.JSONDecodeError:
flask.abort(400)
except KeyError:
flask.abort(400)
if len(username) > 24 or len(username) < 1:
flask.abort(400)
for i in username:
if i not in "abcdefghijklmnopqrstuvwxyz0123456789_-":
return {
2024-02-20 14:55:22 -05:00
"valid": False,
"reason": "User doesn't exist."
}
2024-02-20 14:55:22 -05:00
try:
open(f"{SAVING_DIRECTORY}{username}.json", "r")
except FileNotFoundError:
return {
2024-02-20 14:55:22 -05:00
"valid": False,
"reason": "User doesn't exist."
}
2024-02-20 14:55:22 -05:00
token = generate_token(username, passhash)
try:
enforced_username = open(f"{SAVING_DIRECTORY}tokens/{token}.txt", "r").read()
except FileNotFoundError:
return {
2024-02-20 14:55:22 -05:00
"valid": False,
"reason": "Invalid password"
}
2024-02-20 14:55:22 -05:00
if enforced_username != username:
return {
2024-02-20 14:55:22 -05:00
"valid": False,
"reason": "Invalid password"
}
2024-02-20 14:55:22 -05:00
return {
2024-02-20 14:55:22 -05:00
"valid": True,
"token": token
}
2024-02-20 14:55:22 -05:00
def api_account_signup():
try:
x = json.loads(request.data)
2024-02-20 19:09:45 -05:00
username = x["username"].replace(" ", "").lower()
2024-02-20 14:55:22 -05:00
passhash = x["password"]
except json.JSONDecodeError:
flask.abort(400)
except KeyError:
flask.abort(400)
2024-02-26 17:33:35 -05:00
if len(x["username"]) > 24 or len(username) < 1:
2024-02-20 14:55:22 -05:00
flask.abort(400)
if len(passhash) != 64:
flask.abort(400)
for i in passhash:
if i not in "abcdef0123456789":
flask.abort(400)
for i in username:
if i not in "abcdefghijklmnopqrstuvwxyz0123456789_-":
return {
2024-02-20 14:55:22 -05:00
"valid": False,
"reason": "Username can only contain a-z, 0-9, underscores, and hyphens."
}
2024-02-20 14:55:22 -05:00
try:
open(f"{SAVING_DIRECTORY}{username}.json", "r")
return {
2024-02-20 14:55:22 -05:00
"valid": False,
"reason": "Username taken."
}
2024-02-20 14:55:22 -05:00
except FileNotFoundError:
pass
token = generate_token(username, passhash)
ensure_file(f"{SAVING_DIRECTORY}tokens/{token}.txt", default_value=username)
ensure_file(f"{SAVING_DIRECTORY}{username}.json", default_value=json.dumps({
"username": username,
2024-02-22 19:42:05 -05:00
"display_name": x["username"],
2024-02-20 14:55:22 -05:00
"description": "",
"colors": {
"accent": "#ff0000",
"text": "#ffffff",
"background": "#111122"
},
"names": [
2024-02-20 19:09:45 -05:00
[x["username"], "4"]
2024-02-20 14:55:22 -05:00
],
"pronouns": [
["he/him", "3"],
["it/its", "3"],
["she/her", "3"],
["they/them", "3"]
],
"honorifics": [
["ma'am", "3"],
["madam", "3"],
["mir", "3"],
["mr.", "3"],
["ms.", "3"],
["mx.", "3"],
["sai", "3"],
["shazam", "3"],
["sir", "3"],
["zam", "3"]
],
"compliments": [
["cute", "3"],
["handsome", "3"],
["hot", "3"],
["pretty", "3"],
["sexy", "3"]
2024-02-22 19:42:05 -05:00
],
"relationship": [
2024-02-20 14:55:22 -05:00
["beloved", "3"],
["boyfriend", "3"],
["darling", "3"],
["enbyfriend", "3"],
["friend", "3"],
["girlfriend", "3"],
["husband", "3"],
["partner", "3"],
["wife", "3"]
]
}))
return return_dynamic_content_type(json.dumps({
"valid": True,
"token": token
}))
def api_account_info_(user):
try:
return return_dynamic_content_type(
open(f"{SAVING_DIRECTORY}{user}.json").read(),
"application/json"
)
except FileNotFoundError:
flask.abort(404)
def api_account_self():
try:
return return_dynamic_content_type(
open(SAVING_DIRECTORY + open(f'{SAVING_DIRECTORY}tokens/{request.cookies["token"]}.txt', 'r').read() + ".json", 'r').read(),
"application/json"
)
except FileNotFoundError:
flask.abort(404)
def api_account_change():
username = open(f'{SAVING_DIRECTORY}tokens/{request.cookies["token"]}.txt', 'r').read()
x = json.loads(request.data)
if generate_token(username, x["current"]) != request.cookies["token"]:
flask.abort(401)
new = generate_token(username, x["new"])
os.rename(f'{SAVING_DIRECTORY}tokens/{request.cookies["token"]}.txt', f'{SAVING_DIRECTORY}tokens/{new}.txt')
return {
"token": new
}
def api_account_delete():
username = open(f'{SAVING_DIRECTORY}tokens/{request.cookies["token"]}.txt', 'r').read()
x = json.loads(request.data)
token = generate_token(username, x["passhash"])
if generate_token(username, x["passhash"]) != request.cookies["token"]:
flask.abort(401)
os.remove(f"{SAVING_DIRECTORY}tokens/{token}.txt")
os.remove(f"{SAVING_DIRECTORY}{username}.json")
f = json.loads(open(f"{SAVING_DIRECTORY}public/list.json", "r").read())
if username in f:
f.remove(username)
g = open(f"{SAVING_DIRECTORY}public/list.json", "w")
g.write(json.dumps(f))
g.close()
return "200 OK"
2024-02-20 14:55:22 -05:00
def api_save():
username = open(f'{SAVING_DIRECTORY}tokens/{request.cookies["token"]}.txt', 'r').read()
x = json.loads(request.data)
user_data = json.loads(open(f"{SAVING_DIRECTORY}{username}.json", "r").read())
if "display_name" in x and len(x["display_name"]) < 64 and len(x["display_name"]) > 0:
user_data["display_name"] = x["display_name"]
if "description" in x and len(x["description"]) < 512:
user_data["description"] = x["description"]
2024-02-26 17:33:35 -05:00
if "public" in x:
user_data["public"] = bool(x["public"])
public_list = json.loads(open(f"{SAVING_DIRECTORY}public/list.json", "r").read())
if user_data["public"] and username not in public_list:
public_list.append(username)
elif not user_data["public"] and username in public_list:
public_list.remove(username)
f = open(f"{SAVING_DIRECTORY}public/list.json", "w")
f.write(json.dumps(sorted(public_list)))
f.close()
2024-02-20 14:55:22 -05:00
if "colors" in x:
2024-02-22 19:42:05 -05:00
if "accent" in x["colors"] and validate_color(x["colors"]["accent"]):
2024-02-20 14:55:22 -05:00
user_data["colors"]["accent"] = x["colors"]["accent"]
2024-02-22 19:42:05 -05:00
if "background" in x["colors"] and validate_color(x["colors"]["background"]):
2024-02-20 14:55:22 -05:00
user_data["colors"]["background"] = x["colors"]["background"]
2024-02-22 19:42:05 -05:00
if "text" in x["colors"] and validate_color(x["colors"]["text"]):
2024-02-20 14:55:22 -05:00
user_data["colors"]["text"] = x["colors"]["text"]
if "names" in x:
names = []
for i in x["names"]:
if int(i[1]) in [1, 2, 3, 4] and len(i[0]) > 0 and len(i[0]) < 48:
names.append(i)
user_data["names"] = sort_list(names)
if "pronouns" in x:
pronouns = []
for i in x["pronouns"]:
if int(i[1]) in [1, 2, 3, 4] and len(i[0]) > 0 and len(i[0]) < 48:
pronouns.append(i)
user_data["pronouns"] = sort_list(pronouns)
if "honorifics" in x:
honorifics = []
for i in x["honorifics"]:
if int(i[1]) in [1, 2, 3, 4] and len(i[0]) > 0 and len(i[0]) < 48:
honorifics.append(i)
user_data["honorifics"] = sort_list(honorifics)
if "compliments" in x:
compliments = []
for i in x["compliments"]:
if int(i[1]) in [1, 2, 3, 4] and len(i[0]) > 0 and len(i[0]) < 48:
compliments.append(i)
user_data["compliments"] = sort_list(compliments)
if "relationship" in x:
relationship = []
for i in x["relationship"]:
if int(i[1]) in [1, 2, 3, 4] and len(i[0]) > 0 and len(i[0]) < 48:
relationship.append(i)
user_data["relationship"] = sort_list(relationship)
f = open(f"{SAVING_DIRECTORY}{username}.json", "w")
f.write(json.dumps(user_data))
f.close()
return "200 OK"
2024-02-26 17:33:35 -05:00
def api_browse():
return list_public(
request.args.get("sort"), # type: ignore
int(request.args.get("page")) if "page" in request.args else 0 # type: ignore
2024-02-26 17:33:35 -05:00
)
def home():
if "token" not in request.cookies:
return flask.send_file(f"{CONTENT_DIRECTORY}home.html")
token = request.cookies['token']
if len(token) != 64:
return flask.send_file(f"{CONTENT_DIRECTORY}home.html")
for i in token:
if i not in "abcdef0123456789":
return flask.send_file(f"{CONTENT_DIRECTORY}home.html")
try:
username = open(f"{SAVING_DIRECTORY}tokens/{token}.txt", "r").read()
user_info = json.loads(open(f"{SAVING_DIRECTORY}{username}.json", "r").read())
except FileNotFoundError:
return flask.send_file(f"{CONTENT_DIRECTORY}home.html")
x = open(f"{CONTENT_DIRECTORY}home.html", "r").read()
x = x.replace("{{USERNAME}}", username)
x = x.replace("{{DISPL_NAME}}", user_info["display_name"])
x = x.replace("{{PUBLIC}}", "true" if "public" in user_info and user_info["public"] else "false")
x = x.replace("{{TOTAL}}", str(len(json.loads(open(f"{SAVING_DIRECTORY}public/list.json", "r").read()))))
return return_dynamic_content_type(x, "text/html")
2024-02-20 14:55:22 -05:00
ensure_file(SAVING_DIRECTORY, folder=True)
ensure_file(f"{SAVING_DIRECTORY}tokens/", folder=True)
2024-02-26 17:33:35 -05:00
ensure_file(f"{SAVING_DIRECTORY}public", folder=True)
ensure_file(f"{SAVING_DIRECTORY}public/list.json", default_value="[]")
2024-02-20 14:55:22 -05:00
app.route("/")(create_file_serve("index.html"))
app.route("/login")(create_file_serve("login.html"))
app.route("/signup")(create_file_serve("signup.html"))
app.route("/logout")(create_file_serve("logout.html"))
2024-02-26 17:33:35 -05:00
app.route("/browse")(create_file_serve("browse.html"))
app.route("/settings")(create_file_serve("settings.html"))
2024-02-26 17:33:35 -05:00
2024-02-20 14:55:22 -05:00
app.route("/editor")(create_file_serve("editor.html"))
2024-03-19 18:42:28 -04:00
app.route("/u/<path:user>")(get_user_page)
2024-02-26 17:33:35 -05:00
app.route("/home")(home)
2024-02-20 14:55:22 -05:00
app.route("/js/<path:file>")(create_folder_serve("js"))
app.route("/css/<path:file>")(create_folder_serve("css"))
app.route("/api/account/login", methods=["POST"])(api_account_login)
app.route("/api/account/signup", methods=["POST"])(api_account_signup)
app.route("/api/account/info/<path:user>", methods=["GET"])(api_account_info_)
app.route("/api/account/self", methods=["GET"])(api_account_self)
app.route("/api/account/change", methods=["POST"])(api_account_change)
app.route("/api/account/delete", methods=["DELETE"])(api_account_delete)
2024-02-20 14:55:22 -05:00
app.route("/api/save", methods=["PATCH"])(api_save)
2024-02-26 17:33:35 -05:00
app.route("/api/browse", methods=["GET"])(api_browse)
app.errorhandler(404)(create_file_serve("404.html"))
2024-02-20 14:55:22 -05:00
2024-02-20 19:26:24 -05:00
if UPGRADE_TO_HTTPS:
app.wsgi_app = ProxyFix(app.wsgi_app)
@app.before_request
def enforce_https():
if not request.is_secure:
url = request.url.replace('http://', 'https://', 1)
return redirect(url, code=301)
2024-02-20 14:55:22 -05:00
if __name__ == "__main__":
app.run(debug=True, port=8080)