Implement outboxes

This commit is contained in:
silverpill 2021-12-23 21:51:01 +00:00
parent ca5640b138
commit 91a91b9c16
4 changed files with 113 additions and 12 deletions

View file

@ -136,10 +136,13 @@ fn create_activity(
instance_url,
actor_name,
);
let activity_id = get_object_url(
let mut activity_id = get_object_url(
instance_url,
internal_activity_id.unwrap_or(&new_uuid()),
);
if activity_type == CREATE {
activity_id.push_str("/create");
};
Activity {
context: json!(AP_CONTEXT),
id: activity_id,
@ -241,7 +244,7 @@ pub fn create_activity_note(
instance_url,
&post.author.username,
CREATE,
None,
Some(&post.id),
object,
recipients,
);

View file

@ -2,7 +2,7 @@ use serde::Serialize;
use serde_json::{json, Value};
use super::constants::AP_CONTEXT;
use super::vocabulary::ORDERED_COLLECTION;
use super::vocabulary::{ORDERED_COLLECTION, ORDERED_COLLECTION_PAGE};
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
@ -14,14 +14,53 @@ pub struct OrderedCollection {
#[serde(rename = "type")]
pub object_type: String,
#[serde(skip_serializing_if = "Option::is_none")]
first: Option<String>,
}
impl OrderedCollection {
pub fn new(collection_id: String) -> Self {
pub fn new(
collection_id: String,
first_page_id: Option<String>,
) -> Self {
Self {
context: json!(AP_CONTEXT),
id: collection_id,
object_type: ORDERED_COLLECTION.to_string(),
first: first_page_id,
}
}
}
pub const COLLECTION_PAGE_SIZE: i64 = 10;
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct OrderedCollectionPage {
#[serde(rename = "@context")]
pub context: Value,
pub id: String,
#[serde(rename = "type")]
pub object_type: String,
ordered_items: Vec<Value>,
}
impl OrderedCollectionPage {
pub fn new(
collection_page_id: String,
items: Vec<impl Serialize>,
) -> Self {
let ordered_items = items.into_iter()
.map(|item| json!(item)).collect();
Self {
context: json!(AP_CONTEXT),
id: collection_page_id,
object_type: ORDERED_COLLECTION_PAGE.to_string(),
ordered_items,
}
}
}

View file

@ -10,11 +10,15 @@ use crate::database::{Pool, get_database_client};
use crate::errors::HttpError;
use crate::frontend::{get_post_page_url, get_profile_page_url};
use crate::http_signatures::verify::verify_http_signature;
use crate::models::posts::queries::get_thread;
use crate::models::posts::queries::{get_posts_by_author, get_thread};
use crate::models::users::queries::get_user_by_name;
use super::activity::create_note;
use super::activity::{create_note, create_activity_note};
use super::actor::{get_local_actor, get_instance_actor};
use super::collections::OrderedCollection;
use super::collections::{
COLLECTION_PAGE_SIZE,
OrderedCollection,
OrderedCollectionPage,
};
use super::constants::ACTIVITY_CONTENT_TYPE;
use super::receiver::receive_activity;
use super::vocabulary::DELETE;
@ -116,7 +120,60 @@ async fn inbox(
#[derive(Deserialize)]
struct CollectionQueryParams {
page: Option<i32>,
page: Option<bool>,
}
#[get("/outbox")]
async fn outbox(
config: web::Data<Config>,
db_pool: web::Data<Pool>,
web::Path(username): web::Path<String>,
query_params: web::Query<CollectionQueryParams>,
) -> Result<HttpResponse, HttpError> {
let instance = config.instance();
let collection_id = get_outbox_url(&instance.url(), &username);
let first_page_id = format!("{}?page=true", collection_id);
if query_params.page.is_none() {
let collection = OrderedCollection::new(
collection_id,
Some(first_page_id),
);
let response = HttpResponse::Ok()
.content_type(ACTIVITY_CONTENT_TYPE)
.json(collection);
return Ok(response);
};
let db_client = &**get_database_client(&db_pool).await?;
let user = get_user_by_name(db_client, &username).await?;
// Post are ordered by creation date
let posts = get_posts_by_author(
db_client, &user.id,
false, false,
None, COLLECTION_PAGE_SIZE,
).await?;
// TODO: include reposts
let activities: Vec<_> = posts.iter().filter_map(|post| {
match post.repost_of_id {
Some(_) => None,
None => {
let activity = create_activity_note(
&instance.host(),
&instance.url(),
post,
None,
);
Some(activity)
},
}
}).collect();
let collection_page = OrderedCollectionPage::new(
first_page_id,
activities,
);
let response = HttpResponse::Ok()
.content_type(ACTIVITY_CONTENT_TYPE)
.json(collection_page);
Ok(response)
}
#[get("/followers")]
@ -129,8 +186,8 @@ async fn followers_collection(
// Social graph is not available
return Err(HttpError::PermissionError);
}
let collection_url = get_followers_url(&config.instance_url(), &username);
let collection = OrderedCollection::new(collection_url);
let collection_id = get_followers_url(&config.instance_url(), &username);
let collection = OrderedCollection::new(collection_id, None);
let response = HttpResponse::Ok()
.content_type(ACTIVITY_CONTENT_TYPE)
.json(collection);
@ -147,8 +204,8 @@ async fn following_collection(
// Social graph is not available
return Err(HttpError::PermissionError);
}
let collection_url = get_following_url(&config.instance_url(), &username);
let collection = OrderedCollection::new(collection_url);
let collection_id = get_following_url(&config.instance_url(), &username);
let collection = OrderedCollection::new(collection_id, None);
let response = HttpResponse::Ok()
.content_type(ACTIVITY_CONTENT_TYPE)
.json(collection);
@ -159,6 +216,7 @@ pub fn actor_scope() -> Scope {
web::scope("/users/{username}")
.service(actor_view)
.service(inbox)
.service(outbox)
.service(followers_collection)
.service(following_collection)
}

View file

@ -25,6 +25,7 @@ pub const TOMBSTONE: &str = "Tombstone";
// Collections
pub const ORDERED_COLLECTION: &str = "OrderedCollection";
pub const ORDERED_COLLECTION_PAGE: &str = "OrderedCollectionPage";
// Misc
pub const HASHTAG: &str = "Hashtag";