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