Implement outboxes
This commit is contained in:
parent
ca5640b138
commit
91a91b9c16
4 changed files with 113 additions and 12 deletions
|
@ -136,10 +136,13 @@ fn create_activity(
|
||||||
instance_url,
|
instance_url,
|
||||||
actor_name,
|
actor_name,
|
||||||
);
|
);
|
||||||
let activity_id = get_object_url(
|
let mut activity_id = get_object_url(
|
||||||
instance_url,
|
instance_url,
|
||||||
internal_activity_id.unwrap_or(&new_uuid()),
|
internal_activity_id.unwrap_or(&new_uuid()),
|
||||||
);
|
);
|
||||||
|
if activity_type == CREATE {
|
||||||
|
activity_id.push_str("/create");
|
||||||
|
};
|
||||||
Activity {
|
Activity {
|
||||||
context: json!(AP_CONTEXT),
|
context: json!(AP_CONTEXT),
|
||||||
id: activity_id,
|
id: activity_id,
|
||||||
|
@ -241,7 +244,7 @@ pub fn create_activity_note(
|
||||||
instance_url,
|
instance_url,
|
||||||
&post.author.username,
|
&post.author.username,
|
||||||
CREATE,
|
CREATE,
|
||||||
None,
|
Some(&post.id),
|
||||||
object,
|
object,
|
||||||
recipients,
|
recipients,
|
||||||
);
|
);
|
||||||
|
|
|
@ -2,7 +2,7 @@ use serde::Serialize;
|
||||||
use serde_json::{json, Value};
|
use serde_json::{json, Value};
|
||||||
|
|
||||||
use super::constants::AP_CONTEXT;
|
use super::constants::AP_CONTEXT;
|
||||||
use super::vocabulary::ORDERED_COLLECTION;
|
use super::vocabulary::{ORDERED_COLLECTION, ORDERED_COLLECTION_PAGE};
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
|
@ -14,14 +14,53 @@ pub struct OrderedCollection {
|
||||||
|
|
||||||
#[serde(rename = "type")]
|
#[serde(rename = "type")]
|
||||||
pub object_type: String,
|
pub object_type: String,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
first: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OrderedCollection {
|
impl OrderedCollection {
|
||||||
pub fn new(collection_id: String) -> Self {
|
pub fn new(
|
||||||
|
collection_id: String,
|
||||||
|
first_page_id: Option<String>,
|
||||||
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
context: json!(AP_CONTEXT),
|
context: json!(AP_CONTEXT),
|
||||||
id: collection_id,
|
id: collection_id,
|
||||||
object_type: ORDERED_COLLECTION.to_string(),
|
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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,11 +10,15 @@ use crate::database::{Pool, get_database_client};
|
||||||
use crate::errors::HttpError;
|
use crate::errors::HttpError;
|
||||||
use crate::frontend::{get_post_page_url, get_profile_page_url};
|
use crate::frontend::{get_post_page_url, get_profile_page_url};
|
||||||
use crate::http_signatures::verify::verify_http_signature;
|
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 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::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::constants::ACTIVITY_CONTENT_TYPE;
|
||||||
use super::receiver::receive_activity;
|
use super::receiver::receive_activity;
|
||||||
use super::vocabulary::DELETE;
|
use super::vocabulary::DELETE;
|
||||||
|
@ -116,7 +120,60 @@ async fn inbox(
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct CollectionQueryParams {
|
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")]
|
#[get("/followers")]
|
||||||
|
@ -129,8 +186,8 @@ async fn followers_collection(
|
||||||
// Social graph is not available
|
// Social graph is not available
|
||||||
return Err(HttpError::PermissionError);
|
return Err(HttpError::PermissionError);
|
||||||
}
|
}
|
||||||
let collection_url = get_followers_url(&config.instance_url(), &username);
|
let collection_id = get_followers_url(&config.instance_url(), &username);
|
||||||
let collection = OrderedCollection::new(collection_url);
|
let collection = OrderedCollection::new(collection_id, None);
|
||||||
let response = HttpResponse::Ok()
|
let response = HttpResponse::Ok()
|
||||||
.content_type(ACTIVITY_CONTENT_TYPE)
|
.content_type(ACTIVITY_CONTENT_TYPE)
|
||||||
.json(collection);
|
.json(collection);
|
||||||
|
@ -147,8 +204,8 @@ async fn following_collection(
|
||||||
// Social graph is not available
|
// Social graph is not available
|
||||||
return Err(HttpError::PermissionError);
|
return Err(HttpError::PermissionError);
|
||||||
}
|
}
|
||||||
let collection_url = get_following_url(&config.instance_url(), &username);
|
let collection_id = get_following_url(&config.instance_url(), &username);
|
||||||
let collection = OrderedCollection::new(collection_url);
|
let collection = OrderedCollection::new(collection_id, None);
|
||||||
let response = HttpResponse::Ok()
|
let response = HttpResponse::Ok()
|
||||||
.content_type(ACTIVITY_CONTENT_TYPE)
|
.content_type(ACTIVITY_CONTENT_TYPE)
|
||||||
.json(collection);
|
.json(collection);
|
||||||
|
@ -159,6 +216,7 @@ pub fn actor_scope() -> Scope {
|
||||||
web::scope("/users/{username}")
|
web::scope("/users/{username}")
|
||||||
.service(actor_view)
|
.service(actor_view)
|
||||||
.service(inbox)
|
.service(inbox)
|
||||||
|
.service(outbox)
|
||||||
.service(followers_collection)
|
.service(followers_collection)
|
||||||
.service(following_collection)
|
.service(following_collection)
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@ pub const TOMBSTONE: &str = "Tombstone";
|
||||||
|
|
||||||
// Collections
|
// Collections
|
||||||
pub const ORDERED_COLLECTION: &str = "OrderedCollection";
|
pub const ORDERED_COLLECTION: &str = "OrderedCollection";
|
||||||
|
pub const ORDERED_COLLECTION_PAGE: &str = "OrderedCollectionPage";
|
||||||
|
|
||||||
// Misc
|
// Misc
|
||||||
pub const HASHTAG: &str = "Hashtag";
|
pub const HASHTAG: &str = "Hashtag";
|
||||||
|
|
Loading…
Reference in a new issue