vtt/src/ttfrog/web.py

180 lines
5.6 KiB
Python
Raw Normal View History

2025-10-05 00:15:37 -07:00
from flask import Response, g, redirect, render_template, request, session, url_for
2025-09-27 16:20:08 -07:00
from tinydb import where
2025-09-28 14:14:16 -07:00
2025-10-05 00:15:37 -07:00
from ttfrog import app, forms, schema
2025-09-21 22:11:56 -07:00
STATIC = ["static"]
2025-09-21 22:11:56 -07:00
def relative_uri(path: str = ""):
"""
The request's URI relative to the VIEW_URI without the leading '/'.
"""
return (path or request.path).replace(app.config.VIEW_URI, "", 1).strip("/") or "/"
2025-10-04 01:26:09 -07:00
def get_parent(table: str, uri: str):
try:
parent_uri = uri.strip("/").rsplit("/", 1)[0]
except IndexError:
return None
2025-10-05 00:15:37 -07:00
return get_page(parent_uri, table=table if "/" in parent_uri else "Page", create_okay=False)
2025-10-04 01:26:09 -07:00
def get_page(path: str = "", table: str = "Page", create_okay: bool = False):
2025-09-28 14:14:16 -07:00
"""
2025-10-04 01:26:09 -07:00
Get one page, including its members, but not recursively.
2025-09-28 14:14:16 -07:00
"""
2025-10-04 01:26:09 -07:00
uri = path.strip("/") or relative_uri(request.path)
app.web.logger.debug(f"Need a page in {table} for {uri = }")
if table not in app.db.tables():
return None
2025-10-05 00:15:37 -07:00
page = app.db.table(table).get(where("uri") == uri, recurse=False)
if hasattr(page, "acl"):
acl = []
for pointer in page.acl:
table, uid = pointer.split("::")
acl += app.db.table(table).search(where("uid") == uid, recurse=False)
page.acl = acl
if not page:
if not create_okay:
return None
2025-10-05 00:15:37 -07:00
parent = get_parent(table, uri)
if not g.user.can_read(parent, app.db):
return None
return getattr(schema, table)(name=uri.split("/")[-1], body="This page does not exist", parent=parent)
if not g.user.can_read(page, app.db):
return None
if hasattr(page, "members"):
2025-10-04 01:26:09 -07:00
subpages = []
2025-10-05 00:15:37 -07:00
for pointer in page.members:
2025-10-04 01:26:09 -07:00
table, uid = pointer.split("::")
subpages += app.db.table(table).search(where("uid") == uid, recurse=False)
page.members = subpages
return page
2025-09-21 22:11:56 -07:00
2025-09-27 16:20:08 -07:00
def rendered(page: schema.Record, template: str = "page.html"):
if not page:
return Response("Page not found", status=404)
2025-10-06 17:50:10 -07:00
return render_template(template, page=page, app=app, breadcrumbs=breadcrumbs(), user=session["user"], g=g)
def get_static(path):
return Response("OK", status=200)
def breadcrumbs():
"""
2025-10-04 01:26:09 -07:00
Return (uri, name) pairs for the parents leading from the VIEW_URI to the current request.
"""
if app.config.VIEW_URI != "/":
root = get_page()
2025-10-04 01:26:09 -07:00
yield (app.config.VIEW_URI, root.name)
uri = ""
2025-10-04 01:26:09 -07:00
for name in relative_uri().split("/"):
uri = "/".join([uri, name]).lstrip("/")
yield (uri, name)
2025-09-27 16:20:08 -07:00
@app.web.route("/")
def index():
return rendered(get_page(create_okay=False))
2025-09-27 16:20:08 -07:00
2025-10-05 00:15:37 -07:00
def do_login(username: str, password: str) -> bool:
"""
Update the session with the user record if the credenteials are valid.
"""
if not (username and password):
2025-10-06 17:50:10 -07:00
app.web.logger.debug("Need both username and password to login")
2025-10-05 00:15:37 -07:00
return False
2025-10-06 17:50:10 -07:00
user = app.db.User.get(where("name") == username)
2025-10-05 00:15:37 -07:00
if not user:
2025-10-06 17:50:10 -07:00
app.web.logger.debug(f"No user matching {username}")
2025-10-05 00:15:37 -07:00
return False
if not user.check_credentials(username, password):
2025-10-06 17:50:10 -07:00
app.web.logger.debug(f"Invalid credentials for {username}")
2025-10-05 00:15:37 -07:00
return False
app.web.logger.debug(f"Session for {user.name} ({user.doc_id}) started.")
g.user = user
session["user_id"] = g.user.doc_id
session["user"] = dict(g.user.serialize())
return True
@app.web.route("/login", methods=["GET", "POST"])
2025-10-04 10:48:18 -07:00
def login():
app.web.session_interface.regenerate(session)
2025-10-05 00:15:37 -07:00
if request.method == "POST":
username = request.form.get("username")
password = request.form.get("password")
if do_login(username, password):
return redirect(url_for("index"))
g.messages.append(f"Invalid login for {username}")
return rendered(schema.Page(name="Login", title="Please enter your login details"), "login.html")
2025-10-04 10:48:18 -07:00
@app.web.route("/logout")
def logout():
2025-10-05 00:15:37 -07:00
if "user_id" in session:
del session["user_id"]
2025-10-04 10:48:18 -07:00
del g.user
2025-10-04 01:26:09 -07:00
@app.web.route(f"{app.config.VIEW_URI}/<path:table>/<path:path>", methods=["GET"])
2025-10-05 00:15:37 -07:00
@app.web.route(f"{app.config.VIEW_URI}/<path:path>", methods=["GET"], defaults={"table": "Page"})
2025-10-04 01:26:09 -07:00
def view(table, path):
parent = get_parent(table, relative_uri())
2025-10-05 00:15:37 -07:00
print(parent)
return rendered(get_page(request.path, table=table, create_okay=(parent and parent.doc_id is not None)))
2025-10-04 01:26:09 -07:00
@app.web.route(f"{app.config.VIEW_URI}/<path:table>/<path:path>", methods=["POST"])
2025-10-05 00:15:37 -07:00
@app.web.route(f"{app.config.VIEW_URI}/<path:path>", methods=["POST"], defaults={"table": "Page"})
2025-10-04 01:26:09 -07:00
def edit(table, path):
uri = relative_uri()
2025-10-04 01:26:09 -07:00
parent = get_parent(table, uri)
if not parent:
return Response("You cannot create a page at this location.", status=403)
2025-10-04 01:26:09 -07:00
# get or create the docoument at this uri
page = get_page(uri, table=table, create_okay=True)
save_data = getattr(forms, table)(page, request.form).prepare()
# editing existing document
if page.doc_id:
if page.uid != request.form["uid"]:
return Response("Invalid UID.", status=403)
2025-10-04 01:26:09 -07:00
return rendered(app.db.save(save_data))
2025-10-04 01:26:09 -07:00
# saving a new document
return rendered(app.add_member(parent, save_data))
2025-09-28 14:14:16 -07:00
2025-10-04 10:48:18 -07:00
@app.web.before_request
def before_request():
2025-10-05 00:15:37 -07:00
g.messages = []
user_id = session.get("user_id", 1)
g.user = app.db.User.get(doc_id=user_id)
session["user_id"] = user_id
session["user"] = dict(g.user.serialize())
2025-10-04 10:48:18 -07:00
2025-09-28 14:14:16 -07:00
@app.web.after_request
def add_header(r):
r.headers["Cache-Control"] = "no-cache, no-store, must-revalidate, public, max-age=0"
r.headers["Pragma"] = "no-cache"
r.headers["Expires"] = "0"
return r