Federate stickied posts (ref #647)
This commit is contained in:
parent
68bcc26ff6
commit
fb2b1bea32
5 changed files with 121 additions and 12 deletions
|
@ -17,6 +17,7 @@ use crate::{
|
||||||
activity::insert_activity,
|
activity::insert_activity,
|
||||||
community::{Community, CommunityForm},
|
community::{Community, CommunityForm},
|
||||||
community_view::{CommunityFollowerView, CommunityModeratorView},
|
community_view::{CommunityFollowerView, CommunityModeratorView},
|
||||||
|
post::Post,
|
||||||
user::User_,
|
user::User_,
|
||||||
},
|
},
|
||||||
naive_now,
|
naive_now,
|
||||||
|
@ -29,6 +30,7 @@ use activitystreams::{
|
||||||
context,
|
context,
|
||||||
endpoint::EndpointProperties,
|
endpoint::EndpointProperties,
|
||||||
object::properties::ObjectProperties,
|
object::properties::ObjectProperties,
|
||||||
|
primitives::{XsdAnyUri, XsdAnyUriError, XsdString},
|
||||||
Activity,
|
Activity,
|
||||||
Base,
|
Base,
|
||||||
BaseBox,
|
BaseBox,
|
||||||
|
@ -40,6 +42,7 @@ use diesel::PgConnection;
|
||||||
use failure::{Error, _core::fmt::Debug};
|
use failure::{Error, _core::fmt::Debug};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct CommunityQuery {
|
pub struct CommunityQuery {
|
||||||
|
@ -92,7 +95,12 @@ impl ToApub for Community {
|
||||||
.set_endpoints(endpoint_props)?
|
.set_endpoints(endpoint_props)?
|
||||||
.set_followers(self.get_followers_url())?;
|
.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(
|
Ok(Ext3::new(
|
||||||
group,
|
group,
|
||||||
|
@ -124,6 +132,14 @@ impl ActorType for Community {
|
||||||
self.private_key.to_owned().unwrap()
|
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.
|
/// As a local community, accept the follow request from a remote user.
|
||||||
fn send_accept_follow(&self, follow: &Follow, conn: &PgConnection) -> Result<(), Error> {
|
fn send_accept_follow(&self, follow: &Follow, conn: &PgConnection) -> Result<(), Error> {
|
||||||
let actor_uri = follow.actor.as_single_xsd_any_uri().unwrap().to_string();
|
let actor_uri = follow.actor.as_single_xsd_any_uri().unwrap().to_string();
|
||||||
|
@ -288,14 +304,6 @@ impl ActorType for Community {
|
||||||
.collect(),
|
.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 {
|
impl FromApub for CommunityForm {
|
||||||
|
@ -371,13 +379,40 @@ pub async fn get_apub_community_followers(
|
||||||
let oprops: &mut ObjectProperties = collection.as_mut();
|
let oprops: &mut ObjectProperties = collection.as_mut();
|
||||||
oprops
|
oprops
|
||||||
.set_context_xsd_any_uri(context())?
|
.set_context_xsd_any_uri(context())?
|
||||||
.set_id(community.actor_id)?;
|
.set_id(community.get_followers_url())?;
|
||||||
collection
|
collection
|
||||||
.collection_props
|
.collection_props
|
||||||
.set_total_items(community_followers.len() as u64)?;
|
.set_total_items(community_followers.len() as u64)?;
|
||||||
Ok(create_apub_response(&collection))
|
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 {
|
impl Community {
|
||||||
pub fn do_announce<A>(
|
pub fn do_announce<A>(
|
||||||
activity: A,
|
activity: A,
|
||||||
|
@ -411,4 +446,7 @@ impl Community {
|
||||||
|
|
||||||
Ok(HttpResponse::Ok().finish())
|
Ok(HttpResponse::Ok().finish())
|
||||||
}
|
}
|
||||||
|
fn featured_collection_address(&self) -> Result<XsdAnyUri, XsdAnyUriError> {
|
||||||
|
XsdAnyUri::from_str(&format!("{}{}", self.actor_id, "/collections/featured"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,17 @@
|
||||||
use crate::db::{category::Category, Crud};
|
use crate::db::{category::Category, Crud};
|
||||||
use activitystreams::{ext::Extension, Actor};
|
use activitystreams::{ext::Extension, primitives::XsdAnyUri, Actor};
|
||||||
use diesel::PgConnection;
|
use diesel::PgConnection;
|
||||||
use failure::Error;
|
use failure::Error;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
// TODO: need to specify these fields in the context
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct GroupExtension {
|
pub struct GroupExtension {
|
||||||
pub category: GroupCategory,
|
pub category: GroupCategory,
|
||||||
pub sensitive: bool,
|
pub sensitive: bool,
|
||||||
|
pub featured: XsdAnyUri,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||||
|
@ -24,6 +27,7 @@ impl GroupExtension {
|
||||||
conn: &PgConnection,
|
conn: &PgConnection,
|
||||||
category_id: i32,
|
category_id: i32,
|
||||||
sensitive: bool,
|
sensitive: bool,
|
||||||
|
featured: XsdAnyUri,
|
||||||
) -> Result<GroupExtension, Error> {
|
) -> Result<GroupExtension, Error> {
|
||||||
let category = Category::read(conn, category_id)?;
|
let category = Category::read(conn, category_id)?;
|
||||||
let group_category = GroupCategory {
|
let group_category = GroupCategory {
|
||||||
|
@ -33,6 +37,7 @@ impl GroupExtension {
|
||||||
Ok(GroupExtension {
|
Ok(GroupExtension {
|
||||||
category: group_category,
|
category: group_category,
|
||||||
sensitive,
|
sensitive,
|
||||||
|
featured,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,7 @@ use crate::{
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
apub::{
|
apub::{
|
||||||
|
extensions::group_extensions::GroupExtension,
|
||||||
get_apub_protocol_string,
|
get_apub_protocol_string,
|
||||||
is_apub_id_valid,
|
is_apub_id_valid,
|
||||||
FromApub,
|
FromApub,
|
||||||
|
@ -38,6 +39,7 @@ use crate::{
|
||||||
},
|
},
|
||||||
db::user_view::UserView,
|
db::user_view::UserView,
|
||||||
};
|
};
|
||||||
|
use activitystreams::{collection::UnorderedCollection, primitives::XsdString};
|
||||||
use chrono::NaiveDateTime;
|
use chrono::NaiveDateTime;
|
||||||
|
|
||||||
static ACTOR_REFETCH_INTERVAL_SECONDS: i64 = 24 * 60 * 60;
|
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 group = fetch_remote_object::<GroupExt>(&Url::parse(apub_id)?)?;
|
||||||
let mut cf = CommunityForm::from_apub(&group, conn)?;
|
let mut cf = CommunityForm::from_apub(&group, conn)?;
|
||||||
cf.last_refreshed_at = Some(naive_now());
|
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 {
|
} else {
|
||||||
Ok(c)
|
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 group = fetch_remote_object::<GroupExt>(&Url::parse(apub_id)?)?;
|
||||||
let cf = CommunityForm::from_apub(&group, conn)?;
|
let cf = CommunityForm::from_apub(&group, conn)?;
|
||||||
let community = Community::create(conn, &cf)?;
|
let community = Community::create(conn, &cf)?;
|
||||||
|
fetch_community_stickied_posts(&group.ext_one, community.id, &conn)?;
|
||||||
|
|
||||||
// Also add the community moderators too
|
// Also add the community moderators too
|
||||||
let creator_and_moderator_uris = group
|
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> {
|
fn upsert_post(post_form: &PostForm, conn: &PgConnection) -> Result<Post, Error> {
|
||||||
let existing = Post::read_from_apub_id(conn, &post_form.ap_id);
|
let existing = Post::read_from_apub_id(conn, &post_form.ap_id);
|
||||||
match existing {
|
match existing {
|
||||||
|
|
|
@ -70,6 +70,41 @@ impl Post {
|
||||||
.load::<Self>(conn)
|
.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> {
|
pub fn read_from_apub_id(conn: &PgConnection, object_id: &str) -> Result<Self, Error> {
|
||||||
use crate::schema::post::dsl::*;
|
use crate::schema::post::dsl::*;
|
||||||
post.filter(ap_id.eq(object_id)).first::<Self>(conn)
|
post.filter(ap_id.eq(object_id)).first::<Self>(conn)
|
||||||
|
|
|
@ -28,6 +28,10 @@ pub fn config(cfg: &mut web::ServiceConfig) {
|
||||||
"/c/{community_name}/followers",
|
"/c/{community_name}/followers",
|
||||||
web::get().to(get_apub_community_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
|
// TODO This is only useful for history which we aren't doing right now
|
||||||
// .route(
|
// .route(
|
||||||
// "/c/{community_name}/outbox",
|
// "/c/{community_name}/outbox",
|
||||||
|
|
Loading…
Reference in a new issue