🐸 Frogue(Fed)

~ololduck/Frogue

Raw

//! ActivityPub outbox serving.
//!
//! Serves the outbox collection and individual pages for an actor.

use sea_orm::{
    ColumnTrait, DatabaseConnection, EntityTrait, PaginatorTrait, QueryFilter, QueryOrder,
    QuerySelect,
};

use crate::activitypub::types::{CONTEXT, OrderedCollection, OrderedCollectionPage};
use crate::entities::ap_activities;
use crate::entities::prelude::ApActivities;

/// Serves the outbox collection for an actor.
pub async fn get_outbox(
    db: &DatabaseConnection,
    actor_id: i64,
    outbox_url: &str,
    page: Option<u64>,
) -> Result<serde_json::Value, sea_orm::DbErr> {
    // Count total activities
    let total = ApActivities::find()
        .filter(ap_activities::Column::ActorId.eq(actor_id))
        .count(db)
        .await?;

    // If no page parameter, return the collection
    let Some(page_num) = page else {
        let collection = OrderedCollection {
            context: CONTEXT.to_vec(),
            id: outbox_url.to_string(),
            kind: "OrderedCollection",
            total_items: total,
            first: if total > 0 {
                Some(format!("{}?page=1", outbox_url))
            } else {
                None
            },
            ordered_items: None,
        };
        return Ok(serde_json::to_value(collection).unwrap());
    };

    // Otherwise, return the page
    const PAGE_SIZE: u64 = 20;
    let offset = (page_num - 1) * PAGE_SIZE;

    let activities = ApActivities::find()
        .filter(ap_activities::Column::ActorId.eq(actor_id))
        .order_by_desc(ap_activities::Column::CreatedAt)
        .offset(offset)
        .limit(PAGE_SIZE)
        .all(db)
        .await?;

    let items: Vec<serde_json::Value> = activities
        .iter()
        .filter_map(|a| serde_json::from_str(&a.activity_json).ok())
        .collect();

    let next_page = if offset + PAGE_SIZE < total {
        Some(format!("{}?page={}", outbox_url, page_num + 1))
    } else {
        None
    };

    let page_url = format!("{}?page={}", outbox_url, page_num);
    let page_obj = OrderedCollectionPage {
        context: CONTEXT.to_vec(),
        id: page_url,
        kind: "OrderedCollectionPage",
        total_items: total,
        part_of: outbox_url.to_string(),
        next: next_page,
        ordered_items: items,
    };

    Ok(serde_json::to_value(page_obj).unwrap())
}