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
|
|
|
|
2025-10-03 16:44:25 -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):
|
2025-10-03 16:44:25 -07:00
|
|
|
try:
|
2025-10-04 09:00:50 -07:00
|
|
|
parent_uri = uri.strip("/").rsplit("/", 1)[0]
|
2025-10-03 16:44:25 -07:00
|
|
|
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-03 16:44:25 -07:00
|
|
|
|
2025-10-04 01:26:09 -07:00
|
|
|
|
2025-10-12 15:36:38 -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-12 15:36:38 -07:00
|
|
|
uri = relative_uri(path)
|
|
|
|
|
|
2025-10-04 01:26:09 -07:00
|
|
|
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 not page:
|
2025-10-03 16:44:25 -07:00
|
|
|
if not create_okay:
|
|
|
|
|
return None
|
2025-10-05 00:15:37 -07:00
|
|
|
parent = get_parent(table, uri)
|
2025-10-07 01:18:36 -07:00
|
|
|
if not app.authorize(g.user, parent, schema.Permissions.READ):
|
2025-10-05 00:15:37 -07:00
|
|
|
return None
|
|
|
|
|
return getattr(schema, table)(name=uri.split("/")[-1], body="This page does not exist", parent=parent)
|
|
|
|
|
|
2025-10-07 01:18:36 -07:00
|
|
|
if not app.authorize(g.user, page, schema.Permissions.READ):
|
2025-10-05 00:15:37 -07:00
|
|
|
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-08 00:46:09 -07:00
|
|
|
table, pkey, pval = pointer.split("::")
|
|
|
|
|
subpages += app.db.table(table).search(where(pkey) == pval, recurse=False)
|
2025-10-04 01:26:09 -07:00
|
|
|
page.members = subpages
|
|
|
|
|
|
|
|
|
|
return page
|
2025-09-21 22:11:56 -07:00
|
|
|
|
2025-09-27 16:20:08 -07:00
|
|
|
|
2025-10-03 16:44:25 -07:00
|
|
|
def rendered(page: schema.Record, template: str = "page.html"):
|
|
|
|
|
if not page:
|
|
|
|
|
return Response("Page not found", status=404)
|
|
|
|
|
|
2025-10-12 15:36:38 -07:00
|
|
|
return render_template(
|
|
|
|
|
template,
|
|
|
|
|
page=page,
|
|
|
|
|
app=app,
|
|
|
|
|
breadcrumbs=breadcrumbs(),
|
|
|
|
|
root=g.root,
|
|
|
|
|
user=g.user,
|
|
|
|
|
g=g
|
|
|
|
|
)
|
2025-10-03 16:44:25 -07:00
|
|
|
|
|
|
|
|
|
|
|
|
|
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.
|
2025-10-03 16:44:25 -07:00
|
|
|
"""
|
|
|
|
|
uri = ""
|
2025-10-04 01:26:09 -07:00
|
|
|
for name in relative_uri().split("/"):
|
2025-10-12 15:36:38 -07:00
|
|
|
uri = "/".join([uri, name])
|
2025-10-04 01:26:09 -07:00
|
|
|
yield (uri, name)
|
2025-10-03 16:44:25 -07:00
|
|
|
|
|
|
|
|
|
2025-10-12 15:36:38 -07:00
|
|
|
@app.web.route(app.config.VIEW_URI)
|
2025-09-27 16:20:08 -07:00
|
|
|
def index():
|
2025-10-12 15:36:38 -07:00
|
|
|
return rendered(get_page(app.config.VIEW_URI, create_okay=False))
|
2025-09-27 16:20:08 -07:00
|
|
|
|
|
|
|
|
|
2025-10-05 00:15:37 -07:00
|
|
|
@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")
|
2025-10-07 01:18:36 -07:00
|
|
|
user = app.authenticate(username, password)
|
|
|
|
|
if user:
|
|
|
|
|
g.user = user
|
|
|
|
|
session["user_id"] = g.user.doc_id
|
|
|
|
|
session["user"] = dict(g.user.serialize())
|
2025-10-05 00:15:37 -07:00
|
|
|
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-08 00:46:09 -07:00
|
|
|
return redirect(url_for("index"))
|
2025-10-04 10:48:18 -07:00
|
|
|
|
|
|
|
|
|
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):
|
2025-10-04 09:00:50 -07:00
|
|
|
parent = get_parent(table, relative_uri())
|
2025-10-12 15:36:38 -07:00
|
|
|
page = get_page(request.path, table=table, create_okay=(parent and parent.doc_id is not None))
|
|
|
|
|
return rendered(page)
|
2025-10-03 16:44:25 -07:00
|
|
|
|
|
|
|
|
|
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):
|
2025-10-03 16:44:25 -07:00
|
|
|
uri = relative_uri()
|
2025-10-04 01:26:09 -07:00
|
|
|
parent = get_parent(table, uri)
|
2025-10-03 16:44:25 -07:00
|
|
|
if not parent:
|
2025-10-04 09:00:50 -07:00
|
|
|
return Response("You cannot create a page at this location.", status=403)
|
2025-10-03 16:44:25 -07:00
|
|
|
|
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)
|
2025-10-07 01:18:36 -07:00
|
|
|
if not app.authorize(g.user, page, schema.Permissions.WRITE):
|
|
|
|
|
return Response("Permission denied.", status=403)
|
2025-10-04 01:26:09 -07:00
|
|
|
save_data = getattr(forms, table)(page, request.form).prepare()
|
|
|
|
|
|
|
|
|
|
# editing existing document
|
2025-10-03 16:44:25 -07:00
|
|
|
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-03 16:44:25 -07:00
|
|
|
|
2025-10-04 01:26:09 -07:00
|
|
|
# saving a new document
|
2025-10-07 01:18:36 -07:00
|
|
|
return rendered(parent.add_member(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-12 15:36:38 -07:00
|
|
|
g.root = get_page(app.config.VIEW_URI)
|
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
|