diff --git a/app/admin.py b/app/admin.py index c72d3d0..a736d8b 100644 --- a/app/admin.py +++ b/app/admin.py @@ -229,11 +229,17 @@ def admin_object( ap_id: str, db: Session = Depends(get_db), ) -> templates.TemplateResponse: + requested_object = boxes.get_anybox_object_by_ap_id(db, ap_id) + if not requested_object: + raise HTTPException(status_code=404) + + replies_tree = boxes.get_replies_tree(db, requested_object) + return templates.render_template( db, request, - "admin_object.html", - {}, + "object.html", + {"replies_tree": replies_tree}, ) diff --git a/app/boxes.py b/app/boxes.py index 3da816e..b879f8e 100644 --- a/app/boxes.py +++ b/app/boxes.py @@ -1,5 +1,7 @@ """Actions related to the AP inbox/outbox.""" import uuid +from collections import defaultdict +from dataclasses import dataclass from urllib.parse import urlparse import httpx @@ -732,3 +734,78 @@ def fetch_collection(db: Session, url: str) -> list[ap.RawObject]: raise ValueError(f"internal collection for {url}) not supported") return ap.parse_collection(url) + + +@dataclass +class ReplyTreeNode: + ap_object: AnyboxObject + children: list["ReplyTreeNode"] + is_requested: bool = False + is_root: bool = False + + +def get_replies_tree( + db: Session, + requested_object: AnyboxObject, +) -> ReplyTreeNode: + # TODO: handle visibility + tree_nodes: list[AnyboxObject] = [] + tree_nodes.extend( + db.query(models.InboxObject) + .filter( + models.InboxObject.ap_context == requested_object.ap_context, + ) + .all() + ) + tree_nodes.extend( + db.query(models.OutboxObject) + .filter( + models.OutboxObject.ap_context == requested_object.ap_context, + models.OutboxObject.is_deleted.is_(False), + ) + .all() + ) + nodes_by_in_reply_to = defaultdict(list) + for node in tree_nodes: + nodes_by_in_reply_to[node.in_reply_to].append(node) + logger.info(nodes_by_in_reply_to) + + # TODO: get oldest if we cannot get to root? + if len(nodes_by_in_reply_to.get(None, [])) != 1: + raise ValueError("Failed to compute replies tree") + + def _get_reply_node_children( + node: ReplyTreeNode, + index: defaultdict[str | None, list[AnyboxObject]], + ) -> list[ReplyTreeNode]: + children = [] + for child in index.get(node.ap_object.ap_id, []): # type: ignore + child_node = ReplyTreeNode( + ap_object=child, + is_requested=child.ap_id == requested_object.ap_id, # type: ignore + children=[], + ) + child_node.children = _get_reply_node_children(child_node, index) + children.append(child_node) + + return sorted( + children, + key=lambda node: node.ap_object.ap_published_at, # type: ignore + ) + + if None in nodes_by_in_reply_to: + root_ap_object = nodes_by_in_reply_to[None][0] + else: + root_ap_object = sorted( + tree_nodes, + lambda ap_obj: ap_obj.ap_published_at, # type: ignore + )[0] + + root_node = ReplyTreeNode( + ap_object=root_ap_object, + is_root=True, + is_requested=root_ap_object.ap_id == requested_object.ap_id, + children=[], + ) + root_node.children = _get_reply_node_children(root_node, nodes_by_in_reply_to) + return root_node diff --git a/app/main.py b/app/main.py index 7a8bad8..a36fc8e 100644 --- a/app/main.py +++ b/app/main.py @@ -2,8 +2,6 @@ import base64 import os import sys import time -from collections import defaultdict -from dataclasses import dataclass from datetime import datetime from io import BytesIO from typing import Any @@ -366,81 +364,6 @@ def outbox( ) -@dataclass -class ReplyTreeNode: - ap_object: boxes.AnyboxObject - children: list["ReplyTreeNode"] - is_requested: bool = False - is_root: bool = False - - -def get_replies_tree( - db: Session, - requested_object: boxes.AnyboxObject, -) -> ReplyTreeNode: - # TODO: handle visibility - tree_nodes: list[boxes.AnyboxObject] = [] - tree_nodes.extend( - db.query(models.InboxObject) - .filter( - models.InboxObject.ap_context == requested_object.ap_context, - ) - .all() - ) - tree_nodes.extend( - db.query(models.OutboxObject) - .filter( - models.OutboxObject.ap_context == requested_object.ap_context, - models.OutboxObject.is_deleted.is_(False), - ) - .all() - ) - nodes_by_in_reply_to = defaultdict(list) - for node in tree_nodes: - nodes_by_in_reply_to[node.in_reply_to].append(node) - logger.info(nodes_by_in_reply_to) - - # TODO: get oldest if we cannot get to root? - if len(nodes_by_in_reply_to.get(None, [])) != 1: - raise ValueError("Failed to compute replies tree") - - def _get_reply_node_children( - node: ReplyTreeNode, - index: defaultdict[str | None, list[boxes.AnyboxObject]], - ) -> list[ReplyTreeNode]: - children = [] - for child in index.get(node.ap_object.ap_id, []): # type: ignore - child_node = ReplyTreeNode( - ap_object=child, - is_requested=child.ap_id == requested_object.ap_id, # type: ignore - children=[], - ) - child_node.children = _get_reply_node_children(child_node, index) - children.append(child_node) - - return sorted( - children, - key=lambda node: node.ap_object.ap_published_at, # type: ignore - ) - - if None in nodes_by_in_reply_to: - root_ap_object = nodes_by_in_reply_to[None][0] - else: - root_ap_object = sorted( - tree_nodes, - lambda ap_obj: ap_obj.ap_published_at, # type: ignore - )[0] - - root_node = ReplyTreeNode( - ap_object=root_ap_object, - is_root=True, - is_requested=root_ap_object.ap_id == requested_object.ap_id, - children=[], - ) - root_node.children = _get_reply_node_children(root_node, nodes_by_in_reply_to) - return root_node - - @app.get("/o/{public_id}") def outbox_by_public_id( public_id: str, @@ -468,7 +391,7 @@ def outbox_by_public_id( if is_activitypub_requested(request): return ActivityPubResponse(maybe_object.ap_object) - replies_tree = get_replies_tree(db, maybe_object) + replies_tree = boxes.get_replies_tree(db, maybe_object) return templates.render_template( db, diff --git a/app/templates/utils.html b/app/templates/utils.html index 59640ed..aaf3df7 100644 --- a/app/templates/utils.html +++ b/app/templates/utils.html @@ -66,6 +66,13 @@ {% endmacro %} +{% macro admin_expand_button(ap_object_id) %} +
+ + +
+{% endmacro %} + {% macro display_actor(actor, actors_metadata) %} {% set metadata = actors_metadata.get(actor.ap_id) %}
@@ -204,6 +211,10 @@
{{ admin_profile_button(object.actor.ap_id) }}
+
+ {{ admin_expand_button(object.ap_id) }} +
+ {% endif %}