aboutsummaryrefslogtreecommitdiff
path: root/webmentions_ssg/views.py
diff options
context:
space:
mode:
Diffstat (limited to 'webmentions_ssg/views.py')
-rw-r--r--webmentions_ssg/views.py160
1 files changed, 160 insertions, 0 deletions
diff --git a/webmentions_ssg/views.py b/webmentions_ssg/views.py
new file mode 100644
index 0000000..5408fa4
--- /dev/null
+++ b/webmentions_ssg/views.py
@@ -0,0 +1,160 @@
+import uuid
+from typing import cast
+from urllib.parse import urlsplit
+
+import sqlalchemy as sa
+import sqlalchemy.exc as sa_exc
+from flask import (
+ Blueprint,
+ Response,
+ abort,
+ flash,
+ redirect,
+ render_template,
+ request,
+ url_for,
+)
+from flask.typing import ResponseReturnValue
+from flask_login import current_user, login_required, login_user, logout_user
+
+from . import DATABASE as db
+from . import forms
+from .models import ReceivedWebmention, User
+from .tasks.receiver import verify_webmention
+
+root_page = Blueprint("root", __name__)
+
+
+@root_page.route("/")
+def index() -> ResponseReturnValue:
+ return redirect(url_for("root.received"))
+
+
+@root_page.route("/login", methods=["GET", "POST"])
+def login() -> ResponseReturnValue:
+ if current_user.is_authenticated:
+ return redirect(url_for("root.index"))
+
+ form = forms.LoginForm()
+
+ if form.validate_on_submit():
+ user = db.session.scalar(
+ sa.select(User).where(User.username == form.username.data)
+ )
+
+ if user is None or not user.check_password(form.password.data):
+ flash("Invalid username or password", "danger")
+ return redirect(url_for("root.login"))
+
+ login_user(user)
+
+ next_page = request.args.get("next")
+ if not next_page or urlsplit(next_page).netloc != "":
+ next_page = url_for("root.index")
+ return redirect(next_page)
+
+ return render_template("login.html", form=form)
+
+
+@root_page.route("/logout")
+def logout() -> ResponseReturnValue:
+ logout_user()
+ return redirect(url_for("root.index"))
+
+
+@root_page.route("/received")
+@login_required
+def received() -> ResponseReturnValue:
+ webmentions = db.paginate(
+ sa.select(ReceivedWebmention).order_by(ReceivedWebmention.uuid.desc()),
+ per_page=25,
+ )
+
+ return render_template(
+ "received.html",
+ title="Received Webmentions",
+ webmentions=webmentions,
+ )
+
+
+@root_page.route("/endpoint", methods=["POST"])
+def endpoint() -> ResponseReturnValue:
+ form = forms.EndpointForm(meta={"csrf": False})
+
+ if not form.validate_on_submit():
+ return form.errors, 400
+
+ source = cast(str, form.source.data)
+ target = cast(str, form.target.data)
+
+ webmention = db.session.execute(
+ sa.select(ReceivedWebmention).where(
+ ReceivedWebmention.source == source,
+ ReceivedWebmention.target == target,
+ )
+ ).scalar_one_or_none()
+
+ if webmention is None:
+ identifier = uuid.uuid7()
+
+ webmention = ReceivedWebmention(
+ uuid=identifier,
+ source=source,
+ target=target,
+ )
+
+ db.session.add(webmention)
+
+ try:
+ db.session.commit()
+ except sa_exc.IntegrityError:
+ db.session.rollback()
+
+ # Another request may have inserted the same source/target pair
+ # after our SELECT but before our COMMIT.
+ webmention = db.session.execute(
+ sa.select(ReceivedWebmention).where(
+ ReceivedWebmention.source == source,
+ ReceivedWebmention.target == target,
+ )
+ ).scalar_one()
+
+ identifier = webmention.uuid
+ webmention.status = "received"
+ webmention.failure_reason = None
+
+ db.session.commit()
+
+ else:
+ identifier = webmention.uuid
+
+ # Re-sent webmention: re-verify the existing row.
+ webmention.status = "received"
+ webmention.failure_reason = None
+
+ db.session.commit()
+
+ verify_webmention(webmention.uuid)
+
+ status_url = url_for(
+ "root.status",
+ identifier=str(identifier),
+ _external=True,
+ )
+
+ return Response(
+ status=201,
+ headers={"Location": status_url},
+ )
+
+
+@root_page.route("/status/<uuid:identifier>")
+def status(identifier: uuid.UUID) -> ResponseReturnValue:
+ webmention = db.session.get(ReceivedWebmention, identifier)
+
+ if webmention is None:
+ return abort(404)
+
+ return render_template(
+ "status.html", title="Webmention status", webmention=webmention
+ )