mirror of
https://git.sr.ht/~tsileo/microblog.pub
synced 2024-12-22 05:04:27 +00:00
Add admin object page (expanded note)
This commit is contained in:
parent
ba1d3657b9
commit
1f67d4e71c
4 changed files with 97 additions and 80 deletions
10
app/admin.py
10
app/admin.py
|
@ -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},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
77
app/boxes.py
77
app/boxes.py
|
@ -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
|
||||||
|
|
79
app/main.py
79
app/main.py
|
@ -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,
|
||||||
|
|
|
@ -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 %}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue