aboutsummaryrefslogtreecommitdiff
path: root/webmentions_ssg/views.py
diff options
context:
space:
mode:
authorDennis Fink2026-08-15 20:54:53 +0200
committerDennis Fink2026-08-15 20:54:53 +0200
commite0f65d7ff582b32f1c7b5508bba8cda807aaae5f (patch)
tree6693b9fc259bf642e0392b4d8920fbca7ffd6dda /webmentions_ssg/views.py
parentb3e12b975064f0873bdb4d2834cae75925387d6b (diff)
downloadwebmentions-ssg-e0f65d7ff582b32f1c7b5508bba8cda807aaae5f.tar.gz
webmentions-ssg-e0f65d7ff582b32f1c7b5508bba8cda807aaae5f.zip
feat(sender): add outgoing Webmention pipeline
Scan generated h-entry documents for Webmention targets and track source revisions and delivery state in the database. Discover target endpoints, send Webmentions asynchronously, and reconcile updated, removed, restored, and deleted sources across scans. Add authenticated views for inspecting sent Webmentions and make the scanner schedule configurable.
Diffstat (limited to '')
-rw-r--r--webmentions_ssg/views.py85
1 files changed, 46 insertions, 39 deletions
diff --git a/webmentions_ssg/views.py b/webmentions_ssg/views.py
index b52ae07..eeea765 100644
--- a/webmentions_ssg/views.py
+++ b/webmentions_ssg/views.py
@@ -16,10 +16,11 @@ from flask import (
)
from flask.typing import ResponseReturnValue
from flask_login import current_user, login_required, login_user, logout_user
+from sqlalchemy.orm import selectinload
from . import CSRF, forms
from . import DATABASE as db
-from .models import ReceivedWebmention, User
+from .models import ReceivedWebmention, Source, User
from .tasks.receiver import verify_webmention
root_page = Blueprint("root", __name__)
@@ -80,18 +81,13 @@ def received() -> ResponseReturnValue:
@root_page.post("/received/<uuid:identifier>/delete")
@login_required
-def delete_received_webmention(
- identifier: uuid.UUID,
-) -> ResponseReturnValue:
+def delete_received_webmention(identifier: uuid.UUID) -> ResponseReturnValue:
form = forms.AdminActionForm()
if not form.validate_on_submit():
return abort(400)
- webmention = db.session.get(
- ReceivedWebmention,
- identifier,
- )
+ webmention = db.session.get(ReceivedWebmention, identifier)
if webmention is None:
return abort(404)
@@ -102,27 +98,19 @@ def delete_received_webmention(
flash(f"Webmention {webmention.uuid} deleted.", "success")
return redirect(
- url_for(
- "root.received",
- page=request.args.get("page", 1, type=int),
- )
+ url_for("root.received", page=request.args.get("page", 1, type=int))
)
@root_page.post("/received/<uuid:identifier>/reverify")
@login_required
-def reverify_received_webmention(
- identifier: uuid.UUID,
-) -> ResponseReturnValue:
+def reverify_received_webmention(identifier: uuid.UUID) -> ResponseReturnValue:
form = forms.AdminActionForm()
if not form.validate_on_submit():
return abort(400)
- webmention = db.session.get(
- ReceivedWebmention,
- identifier,
- )
+ webmention = db.session.get(ReceivedWebmention, identifier)
if webmention is None:
return abort(404)
@@ -137,10 +125,41 @@ def reverify_received_webmention(
flash(f"Webmention {webmention.uuid} queued for reverification.", "success")
return redirect(
- url_for(
- "root.received",
- page=request.args.get("page", 1, type=int),
- )
+ url_for("root.received", page=request.args.get("page", 1, type=int))
+ )
+
+
+@root_page.route("/sent")
+@login_required
+def sent() -> ResponseReturnValue:
+ sources = db.paginate(
+ sa.select(Source)
+ .options(selectinload(Source.sent_webmentions))
+ .order_by(Source.last_seen_at.desc()),
+ per_page=25,
+ )
+ return render_template("sent.html", title="Sent Webmentions", sources=sources)
+
+
+@root_page.route("/sent/<uuid:identifier>")
+@login_required
+def sent_source(identifier: uuid.UUID) -> ResponseReturnValue:
+ source = db.session.scalar(
+ sa.select(Source)
+ .options(selectinload(Source.sent_webmentions))
+ .where(Source.uuid == identifier)
+ )
+ if source is None:
+ return abort(404)
+
+ webmentions = sorted(
+ source.sent_webmentions, key=lambda webmention: webmention.target
+ )
+ return render_template(
+ "sent_source.html",
+ title="Sent Webmentions",
+ source=source,
+ webmentions=webmentions,
)
@@ -157,19 +176,14 @@ def endpoint() -> ResponseReturnValue:
webmention = db.session.execute(
sa.select(ReceivedWebmention).where(
- ReceivedWebmention.source == source,
- ReceivedWebmention.target == target,
+ 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,
- )
+ webmention = ReceivedWebmention(uuid=identifier, source=source, target=target)
db.session.add(webmention)
@@ -204,16 +218,9 @@ def endpoint() -> ResponseReturnValue:
verify_webmention(webmention.uuid)
- status_url = url_for(
- "root.status",
- identifier=str(identifier),
- _external=True,
- )
+ status_url = url_for("root.status", identifier=str(identifier), _external=True)
- return Response(
- status=201,
- headers={"Location": status_url},
- )
+ return Response(status=201, headers={"Location": status_url})
@root_page.route("/status/<uuid:identifier>")