Federate stickied posts (ref #647)

This commit is contained in:
Felix Ableitner 2020-06-16 16:03:42 +02:00
parent 68bcc26ff6
commit fb2b1bea32
5 changed files with 121 additions and 12 deletions

View file

@ -17,6 +17,7 @@ use crate::{
activity::insert_activity,
community::{Community, CommunityForm},
community_view::{CommunityFollowerView, CommunityModeratorView},
post::Post,
user::User_,
},
naive_now,
@ -29,6 +30,7 @@ use activitystreams::{
context,
endpoint::EndpointProperties,
object::properties::ObjectProperties,
primitives::{XsdAnyUri, XsdAnyUriError, XsdString},
Activity,
Base,
BaseBox,
@ -40,6 +42,7 @@ use diesel::PgConnection;
use failure::{Error, _core::fmt::Debug};
use itertools::Itertools;
use serde::{Deserialize, Serialize};
use std::str::FromStr;
#[derive(Deserialize)]
pub struct CommunityQuery {
@ -92,7 +95,12 @@ impl ToApub for Community {
.set_endpoints(endpoint_props)?
.set_followers(self.get_followers_url())?;
let group_extension = GroupExtension::new(conn, self.category_id, self.nsfw)?;
let group_extension = GroupExtension::new(
conn,
self.category_id,
self.nsfw,
self.featured_collection_address()?,
)?;
Ok(Ext3::new(
group,
@ -124,6 +132,14 @@ impl ActorType for Community {
self.private_key.to_owned().unwrap()
}
fn send_follow(&self, _follow_actor_id: &str, _conn: &PgConnection) -> Result<(), Error> {
unimplemented!()
}
fn send_unfollow(&self, _follow_actor_id: &str, _conn: &PgConnection) -> Result<(), Error> {
unimplemented!()
}
/// As a local community, accept the follow request from a remote user.
fn send_accept_follow(&self, follow: &Follow, conn: &PgConnection) -> Result<(), Error> {
let actor_uri = follow.actor.as_single_xsd_any_uri().unwrap().to_string();
@ -288,14 +304,6 @@ impl ActorType for Community {
.collect(),
)
}
fn send_follow(&self, _follow_actor_id: &str, _conn: &PgConnection) -> Result<(), Error> {
unimplemented!()
}
fn send_unfollow(&self, _follow_actor_id: &str, _conn: &PgConnection) -> Result<(), Error> {
unimplemented!()
}
}
impl FromApub for CommunityForm {
@ -371,13 +379,40 @@ pub async fn get_apub_community_followers(
let oprops: &mut ObjectProperties = collection.as_mut();
oprops
.set_context_xsd_any_uri(context())?
.set_id(community.actor_id)?;
.set_id(community.get_followers_url())?;
collection
.collection_props
.set_total_items(community_followers.len() as u64)?;
Ok(create_apub_response(&collection))
}
/// TODO
pub async fn get_featured_posts(
info: Path<CommunityQuery>,
db: DbPoolParam,
) -> Result<HttpResponse<Body>, Error> {
let community = Community::read_from_name(&&db.get()?, &info.community_name)?;
let conn = db.get()?;
let featured_posts = Post::list_stickied_for_community(&conn, community.id)?
.iter()
// TODO: should probably be XsdAnyUri but collection doesnt have a setter for that
.map(|p| XsdString::from_str(&p.ap_id))
.collect::<Result<Vec<_>, std::convert::Infallible>>()?;
let mut collection = UnorderedCollection::default();
let oprops: &mut ObjectProperties = collection.as_mut();
oprops
.set_context_xsd_any_uri(context())?
.set_id(community.featured_collection_address()?)?;
collection
.collection_props
.set_total_items(featured_posts.len() as u64)?
.set_many_items_xsd_strings(featured_posts)?;
Ok(create_apub_response(&collection))
}
impl Community {
pub fn do_announce<A>(
activity: A,
@ -411,4 +446,7 @@ impl Community {
Ok(HttpResponse::Ok().finish())
}
fn featured_collection_address(&self) -> Result<XsdAnyUri, XsdAnyUriError> {
XsdAnyUri::from_str(&format!("{}{}", self.actor_id, "/collections/featured"))
}
}

View file

@ -1,14 +1,17 @@
use crate::db::{category::Category, Crud};
use activitystreams::{ext::Extension, Actor};
use activitystreams::{ext::Extension, primitives::XsdAnyUri, Actor};
use diesel::PgConnection;
use failure::Error;
use serde::{Deserialize, Serialize};
// TODO: need to specify these fields in the context
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct GroupExtension {
pub category: GroupCategory,
pub sensitive: bool,
pub featured: XsdAnyUri,
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
@ -24,6 +27,7 @@ impl GroupExtension {
conn: &PgConnection,
category_id: i32,
sensitive: bool,
featured: XsdAnyUri,
) -> Result<GroupExtension, Error> {
let category = Category::read(conn, category_id)?;
let group_category = GroupCategory {
@ -33,6 +37,7 @@ impl GroupExtension {
Ok(GroupExtension {
category: group_category,
sensitive,
featured,
})
}
}

View file

@ -28,6 +28,7 @@ use crate::{
use crate::{
apub::{
extensions::group_extensions::GroupExtension,
get_apub_protocol_string,
is_apub_id_valid,
FromApub,
@ -38,6 +39,7 @@ use crate::{
},
db::user_view::UserView,
};
use activitystreams::{collection::UnorderedCollection, primitives::XsdString};
use chrono::NaiveDateTime;
static ACTOR_REFETCH_INTERVAL_SECONDS: i64 = 24 * 60 * 60;
@ -215,7 +217,9 @@ pub fn get_or_fetch_and_upsert_remote_community(
let group = fetch_remote_object::<GroupExt>(&Url::parse(apub_id)?)?;
let mut cf = CommunityForm::from_apub(&group, conn)?;
cf.last_refreshed_at = Some(naive_now());
Ok(Community::update(&conn, c.id, &cf)?)
let community = Community::update(&conn, c.id, &cf)?;
fetch_community_stickied_posts(&group.ext_one, community.id, &conn)?;
Ok(community)
} else {
Ok(c)
}
@ -225,6 +229,7 @@ pub fn get_or_fetch_and_upsert_remote_community(
let group = fetch_remote_object::<GroupExt>(&Url::parse(apub_id)?)?;
let cf = CommunityForm::from_apub(&group, conn)?;
let community = Community::create(conn, &cf)?;
fetch_community_stickied_posts(&group.ext_one, community.id, &conn)?;
// Also add the community moderators too
let creator_and_moderator_uris = group
@ -250,6 +255,28 @@ pub fn get_or_fetch_and_upsert_remote_community(
}
}
fn fetch_community_stickied_posts(
group_ext: &GroupExtension,
community_id: i32,
conn: &PgConnection,
) -> Result<(), Error> {
let featured =
fetch_remote_object::<UnorderedCollection>(&Url::parse(group_ext.featured.as_str())?)?;
let featured_items = featured
.collection_props
.get_many_items_xsd_strings()
.map(|o| o.collect::<Vec<_>>())
.unwrap_or_default()
.iter()
.map(|i: &&XsdString| -> Result<String, Error> {
get_or_fetch_and_insert_remote_post(i.as_str(), &conn)?;
Ok(i.as_str().to_string())
})
.collect::<Result<Vec<_>, Error>>()?;
Post::set_stickied_for_community(&conn, community_id, featured_items)?;
Ok(())
}
fn upsert_post(post_form: &PostForm, conn: &PgConnection) -> Result<Post, Error> {
let existing = Post::read_from_apub_id(conn, &post_form.ap_id);
match existing {

View file

@ -70,6 +70,41 @@ impl Post {
.load::<Self>(conn)
}
pub fn list_stickied_for_community(
conn: &PgConnection,
the_community_id: i32,
) -> Result<Vec<Self>, Error> {
use crate::schema::post::dsl::*;
post
.filter(community_id.eq(the_community_id))
.filter(stickied.eq(true))
.load::<Self>(conn)
}
pub fn set_stickied_for_community(
conn: &PgConnection,
the_community_id: i32,
stickied_items: Vec<String>,
) -> Result<(), Error> {
use crate::schema::post::dsl::*;
// TODO: surely there is a better way to do this
diesel::update(post)
.filter(community_id.eq(the_community_id))
.set(stickied.eq(false))
.execute(conn)?;
stickied_items
.iter()
.map(|i| {
diesel::update(post)
.filter(community_id.eq(the_community_id))
.filter(ap_id.eq(i))
.set(stickied.eq(true))
.execute(conn)
})
.collect::<Result<Vec<_>, Error>>()?;
Ok(())
}
pub fn read_from_apub_id(conn: &PgConnection, object_id: &str) -> Result<Self, Error> {
use crate::schema::post::dsl::*;
post.filter(ap_id.eq(object_id)).first::<Self>(conn)

View file

@ -28,6 +28,10 @@ pub fn config(cfg: &mut web::ServiceConfig) {
"/c/{community_name}/followers",
web::get().to(get_apub_community_followers),
)
.route(
"/c/{community_name}/collections/featured",
web::get().to(get_featured_posts),
)
// TODO This is only useful for history which we aren't doing right now
// .route(
// "/c/{community_name}/outbox",