use crate::{ apub::{ extensions::signatures::verify, fetcher::{get_or_fetch_and_upsert_community, get_or_fetch_and_upsert_user}, insert_activity, ActorType, }, blocking, routes::{ChatServerParam, DbPoolParam}, LemmyError, }; use activitystreams::{ activity::{Follow, Undo}, prelude::*, }; use actix_web::{client::Client, web, HttpRequest, HttpResponse}; use anyhow::anyhow; use lemmy_db::{ community::{Community, CommunityFollower, CommunityFollowerForm}, user::User_, Followable, }; use log::debug; use serde::Deserialize; use std::fmt::Debug; #[serde(untagged)] #[derive(Deserialize, Debug)] pub enum CommunityAcceptedObjects { Follow(Follow), Undo(Undo), } impl CommunityAcceptedObjects { fn follow(&self) -> Result { match self { CommunityAcceptedObjects::Follow(f) => Ok(f.to_owned()), CommunityAcceptedObjects::Undo(u) => { Ok(Follow::from_any_base(u.object().as_one().unwrap().to_owned())?.unwrap()) } } } } /// Handler for all incoming activities to community inboxes. pub async fn community_inbox( request: HttpRequest, input: web::Json, path: web::Path, db: DbPoolParam, client: web::Data, _chat_server: ChatServerParam, ) -> Result { let input = input.into_inner(); let path = path.into_inner(); let community = blocking(&db, move |conn| Community::read_from_name(&conn, &path)).await??; if !community.local { return Err( anyhow!( "Received activity is addressed to remote community {}", &community.actor_id ) .into(), ); } debug!( "Community {} received activity {:?}", &community.name, &input ); let follow = input.follow()?; let user_uri = follow.actor()?.as_single_xsd_any_uri().unwrap(); let community_uri = follow.object().as_single_xsd_any_uri().unwrap(); let user = get_or_fetch_and_upsert_user(&user_uri, &client, &db).await?; let community = get_or_fetch_and_upsert_community(community_uri, &client, &db).await?; verify(&request, &user)?; match input { CommunityAcceptedObjects::Follow(f) => handle_follow(f, user, community, &client, db).await, CommunityAcceptedObjects::Undo(u) => handle_undo_follow(u, user, community, db).await, } } /// Handle a follow request from a remote user, adding it to the local database and returning an /// Accept activity. async fn handle_follow( follow: Follow, user: User_, community: Community, client: &Client, db: DbPoolParam, ) -> Result { insert_activity(user.id, follow.clone(), false, &db).await?; let community_follower_form = CommunityFollowerForm { community_id: community.id, user_id: user.id, }; // This will fail if they're already a follower, but ignore the error. blocking(&db, move |conn| { CommunityFollower::follow(&conn, &community_follower_form).ok() }) .await?; community.send_accept_follow(follow, &client, &db).await?; Ok(HttpResponse::Ok().finish()) } async fn handle_undo_follow( undo: Undo, user: User_, community: Community, db: DbPoolParam, ) -> Result { insert_activity(user.id, undo, false, &db).await?; let community_follower_form = CommunityFollowerForm { community_id: community.id, user_id: user.id, }; // This will fail if they aren't a follower, but ignore the error. blocking(&db, move |conn| { CommunityFollower::unfollow(&conn, &community_follower_form).ok() }) .await?; Ok(HttpResponse::Ok().finish()) }