From 84c908bb0823885f430f8e778b68d09f076c285c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Madar=C3=A1sz?= Date: Thu, 12 Jan 2023 15:05:15 +0000 Subject: [PATCH] introduce admin snippets --- app/admin.py | 208 +++++++++++++++++++++-- app/templates/admin_direct_messages.html | 4 +- app/templates/admin_snippets.html | 14 ++ app/templates/layout.html | 1 + 4 files changed, 213 insertions(+), 14 deletions(-) create mode 100644 app/templates/admin_snippets.html diff --git a/app/admin.py b/app/admin.py index 0c40ff2..1c08c48 100644 --- a/app/admin.py +++ b/app/admin.py @@ -587,23 +587,24 @@ async def admin_direct_messages( ) ).scalars() ) - has_actors = True # If this message from outbox starts a thread with no replies, look # at the mentions if not actors and anybox_object.is_from_outbox: - has_actors = False - actors = ( # type: ignore - await db_session.execute( - select(models.Actor).where( - models.Actor.ap_id.in_( - mention["href"] - for mention in anybox_object.tags - if mention["type"] == "Mention" + actors = list( + ( # type: ignore + await db_session.execute( + select(models.Actor).where( + models.Actor.ap_id.in_( + mention["href"] + for mention in anybox_object.tags + if mention["type"] == "Mention" + ) ) ) - ) ).scalars() - threads.append((anybox_object, convo, actors, has_actors)) + ) + if actors: + threads.append((anybox_object, convo, actors)) return await templates.render_template( db_session, @@ -614,6 +615,191 @@ async def admin_direct_messages( }, ) +@router.get("/snippets") +async def admin_snippets( + request: Request, + db_session: AsyncSession = Depends(get_db_session), + cursor: str | None = None, +) -> templates.TemplateResponse: + inbox_convos = ( + ( + await db_session.execute( + select( + models.InboxObject.ap_context, + models.InboxObject.actor_id, + func.count(1).label("count"), + func.max(models.InboxObject.ap_published_at).label( + "most_recent_date" + ), + ) + .where( + models.InboxObject.visibility == ap.VisibilityEnum.DIRECT, + models.InboxObject.ap_context.is_not(None), + # Skip transient object like poll relies + models.InboxObject.is_transient.is_(False), + models.InboxObject.is_deleted.is_(False), + ) + .group_by(models.InboxObject.ap_context, models.InboxObject.actor_id) + ) + ) + .unique() + .all() + ) + outbox_convos = ( + ( + await db_session.execute( + select( + models.OutboxObject.ap_context, + func.count(1).label("count"), + func.max(models.OutboxObject.ap_published_at).label( + "most_recent_date" + ), + ) + .where( + models.OutboxObject.visibility == ap.VisibilityEnum.DIRECT, + models.OutboxObject.ap_context.is_not(None), + # Skip transient object like poll relies + models.OutboxObject.is_transient.is_(False), + models.OutboxObject.is_deleted.is_(False), + ) + .group_by(models.OutboxObject.ap_context) + ) + ) + .unique() + .all() + ) + + # Build a "threads index" by combining objects from the inbox and outbox + convos = {} + for inbox_convo in inbox_convos: + if inbox_convo.ap_context not in convos: + convos[inbox_convo.ap_context] = { + "actor_ids": {inbox_convo.actor_id}, + "count": inbox_convo.count, + "most_recent_from_inbox": inbox_convo.most_recent_date, + "most_recent_from_outbox": datetime.min, + } + else: + convos[inbox_convo.ap_context]["actor_ids"].add(inbox_convo.actor_id) + convos[inbox_convo.ap_context]["count"] += inbox_convo.count + convos[inbox_convo.ap_context]["most_recent_from_inbox"] = max( + inbox_convo.most_recent_date, + convos[inbox_convo.ap_context]["most_recent_from_inbox"], + ) + + for outbox_convo in outbox_convos: + if outbox_convo.ap_context not in convos: + convos[outbox_convo.ap_context] = { + "actor_ids": set(), + "count": outbox_convo.count, + "most_recent_from_inbox": datetime.min, + "most_recent_from_outbox": outbox_convo.most_recent_date, + } + else: + convos[outbox_convo.ap_context]["count"] += outbox_convo.count + convos[outbox_convo.ap_context]["most_recent_from_outbox"] = max( + outbox_convo.most_recent_date, + convos[outbox_convo.ap_context]["most_recent_from_outbox"], + ) + + # Fetch the latest object for each threads + convos_with_last_from_inbox = [] + convos_with_last_from_outbox = [] + for context, convo in convos.items(): + if convo["most_recent_from_inbox"] > convo["most_recent_from_outbox"]: + convos_with_last_from_inbox.append( + and_( + models.InboxObject.ap_context == context, + models.InboxObject.ap_published_at + == convo["most_recent_from_inbox"], + ) + ) + else: + convos_with_last_from_outbox.append( + and_( + models.OutboxObject.ap_context == context, + models.OutboxObject.ap_published_at + == convo["most_recent_from_outbox"], + ) + ) + last_from_inbox = ( + ( + ( + await db_session.scalars( + select(models.InboxObject) + .where(or_(*convos_with_last_from_inbox)) + .options( + joinedload(models.InboxObject.actor), + ) + ) + ) + .unique() + .all() + ) + if convos_with_last_from_inbox + else [] + ) + last_from_outbox = ( + ( + ( + await db_session.scalars( + select(models.OutboxObject) + .where(or_(*convos_with_last_from_outbox)) + .options( + joinedload( + models.OutboxObject.outbox_object_attachments + ).options(joinedload(models.OutboxObjectAttachment.upload)), + ) + ) + ) + .unique() + .all() + ) + if convos_with_last_from_outbox + else [] + ) + + # Build the template response + threads = [] + for anybox_object in sorted( + last_from_inbox + last_from_outbox, + key=lambda x: x.ap_published_at, + reverse=True, + ): + convo = convos[anybox_object.ap_context] + actors = list( + ( + await db_session.execute( + select(models.Actor).where(models.Actor.id.in_(convo["actor_ids"])) + ) + ).scalars() + ) + if not actors and anybox_object.is_from_outbox: + actors = list( + ( # type: ignore + await db_session.execute( + select(models.Actor).where( + models.Actor.ap_id.in_( + mention["href"] + for mention in anybox_object.tags + if mention["type"] == "Mention" + ) + ) + ) + ).scalars() + ) + if not actors: + threads.append((anybox_object)) + + return await templates.render_template( + db_session, + request, + "admin_snippets.html", + { + "threads": threads, + }, + ) + @router.get("/outbox") async def admin_outbox( diff --git a/app/templates/admin_direct_messages.html b/app/templates/admin_direct_messages.html index 3a7cdfd..88ab312 100644 --- a/app/templates/admin_direct_messages.html +++ b/app/templates/admin_direct_messages.html @@ -7,15 +7,13 @@ {% block content %} -{% for anybox_object, convo, actors, has_actors in threads %} +{% for anybox_object, convo, actors in threads %}
- {% if has_actors %} With {% for actor in actors %} {{ actor.handle }} {% endfor %} - {% endif %}
{{ utils.display_object(anybox_object) }} {% endfor %} diff --git a/app/templates/admin_snippets.html b/app/templates/admin_snippets.html new file mode 100644 index 0000000..780b81b --- /dev/null +++ b/app/templates/admin_snippets.html @@ -0,0 +1,14 @@ +{%- import "utils.html" as utils with context -%} +{% extends "layout.html" %} + +{% block head %} +blog.zak - Snippets +{% endblock %} + +{% block content %} + +{% for anybox_object in threads %} +
+ {{ utils.display_object(anybox_object) }} +{% endfor %} +{% endblock %} diff --git a/app/templates/layout.html b/app/templates/layout.html index db894a4..f789ae0 100644 --- a/app/templates/layout.html +++ b/app/templates/layout.html @@ -29,6 +29,7 @@
  • {{ admin_link("admin_stream", "Stream") }}
  • {{ admin_link("admin_inbox", "Inbox") }} / {{ admin_link("admin_outbox", "Outbox") }}
  • {{ admin_link("admin_direct_messages", "DMs") }}
  • +
  • {{ admin_link("admin_snippets", "Snippets") }}
  • {{ admin_link("get_notifications", "Notifications") }} {% if notifications_count %}({{ notifications_count }}){% endif %}
  • {{ admin_link("get_lookup", "Lookup") }}
  • {{ admin_link("admin_bookmarks", "Bookmarks") }}