Add admin object page (expanded note)

This commit is contained in:
Thomas Sileo 2022-06-25 12:29:35 +02:00
parent ba1d3657b9
commit 1f67d4e71c
4 changed files with 97 additions and 80 deletions

View file

@ -229,11 +229,17 @@ def admin_object(
ap_id: str, ap_id: str,
db: Session = Depends(get_db), db: Session = Depends(get_db),
) -> templates.TemplateResponse: ) -> 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( return templates.render_template(
db, db,
request, request,
"admin_object.html", "object.html",
{}, {"replies_tree": replies_tree},
) )

View file

@ -1,5 +1,7 @@
"""Actions related to the AP inbox/outbox.""" """Actions related to the AP inbox/outbox."""
import uuid import uuid
from collections import defaultdict
from dataclasses import dataclass
from urllib.parse import urlparse from urllib.parse import urlparse
import httpx 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") raise ValueError(f"internal collection for {url}) not supported")
return ap.parse_collection(url) 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

View file

@ -2,8 +2,6 @@ import base64
import os import os
import sys import sys
import time import time
from collections import defaultdict
from dataclasses import dataclass
from datetime import datetime from datetime import datetime
from io import BytesIO from io import BytesIO
from typing import Any 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}") @app.get("/o/{public_id}")
def outbox_by_public_id( def outbox_by_public_id(
public_id: str, public_id: str,
@ -468,7 +391,7 @@ def outbox_by_public_id(
if is_activitypub_requested(request): if is_activitypub_requested(request):
return ActivityPubResponse(maybe_object.ap_object) 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( return templates.render_template(
db, db,

View file

@ -66,6 +66,13 @@
</form> </form>
{% endmacro %} {% endmacro %}
{% macro admin_expand_button(ap_object_id) %}
<form action="{{ url_for("admin_object") }}" method="GET">
<input type="hidden" name="ap_id" value="{{ ap_object_id }}">
<button type="submit">Expand</button>
</form>
{% endmacro %}
{% macro display_actor(actor, actors_metadata) %} {% macro display_actor(actor, actors_metadata) %}
{% set metadata = actors_metadata.get(actor.ap_id) %} {% set metadata = actors_metadata.get(actor.ap_id) %}
<div style="display: flex;column-gap: 20px;margin:20px 0 10px 0;" class="actor-box"> <div style="display: flex;column-gap: 20px;margin:20px 0 10px 0;" class="actor-box">
@ -204,6 +211,10 @@
<div class="bar-item"> <div class="bar-item">
{{ admin_profile_button(object.actor.ap_id) }} {{ admin_profile_button(object.actor.ap_id) }}
</div> </div>
<div class="bar-item">
{{ admin_expand_button(object.ap_id) }}
</div>
{% endif %} {% endif %}