mirror of
				https://git.sr.ht/~tsileo/microblog.pub
				synced 2025-06-05 21:59:23 +02:00 
			
		
		
		
	Add admin object page (expanded note)
This commit is contained in:
		
							
								
								
									
										10
									
								
								app/admin.py
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								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},
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										77
									
								
								app/boxes.py
									
									
									
									
									
								
							
							
						
						
									
										77
									
								
								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
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										79
									
								
								app/main.py
									
									
									
									
									
								
							
							
						
						
									
										79
									
								
								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,
 | 
			
		||||
 
 | 
			
		||||
@@ -66,6 +66,13 @@
 | 
			
		||||
</form> 
 | 
			
		||||
{% 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) %}
 | 
			
		||||
{% set metadata = actors_metadata.get(actor.ap_id) %}
 | 
			
		||||
<div style="display: flex;column-gap: 20px;margin:20px 0 10px 0;" class="actor-box">
 | 
			
		||||
@@ -204,6 +211,10 @@
 | 
			
		||||
            <div class="bar-item">
 | 
			
		||||
                {{ admin_profile_button(object.actor.ap_id) }}
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="bar-item">
 | 
			
		||||
                {{ admin_expand_button(object.ap_id) }}
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    {% endif %}
 | 
			
		||||
    
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user