2025-04-12 15:16:40 +01:00
|
|
|
use chrono::Local;
|
2025-04-11 15:47:22 +01:00
|
|
|
use main::ap;
|
2025-04-12 15:16:40 +01:00
|
|
|
use rocket::serde::json::serde_json;
|
2025-04-11 15:47:22 +01:00
|
|
|
use rocket::{State, post};
|
|
|
|
use rocket_db_pools::Connection;
|
|
|
|
use sqlx::Sqlite;
|
|
|
|
use url::Url;
|
|
|
|
use uuid::Uuid;
|
2025-04-25 16:46:47 +01:00
|
|
|
use tracing::{event, span, Level, debug, warn, info};
|
2025-04-11 15:47:22 +01:00
|
|
|
|
|
|
|
use crate::{
|
|
|
|
Db,
|
|
|
|
http::HttpClient,
|
2025-04-26 12:17:32 +01:00
|
|
|
types::{Person, content::Post, activity},
|
2025-04-11 15:47:22 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
fn handle_delete_activity(activity: activity::DeleteActivity) {
|
2025-04-25 16:46:47 +01:00
|
|
|
warn!(?activity, "unimplemented delete activity");
|
2025-04-11 15:47:22 +01:00
|
|
|
}
|
|
|
|
|
2025-04-12 15:16:40 +01:00
|
|
|
async fn create_actor(
|
|
|
|
user: &Person,
|
2025-04-25 16:46:47 +01:00
|
|
|
actor: &str,
|
2025-04-12 15:16:40 +01:00
|
|
|
conn: impl sqlx::Executor<'_, Database = Sqlite>,
|
|
|
|
) {
|
2025-04-11 15:47:22 +01:00
|
|
|
sqlx::query!(
|
|
|
|
r#"
|
|
|
|
INSERT INTO actor (id, inbox, outbox)
|
|
|
|
VALUES ( ?1, ?2, ?3 )
|
|
|
|
ON CONFLICT(id) DO NOTHING;
|
|
|
|
"#,
|
|
|
|
actor,
|
|
|
|
user.inbox,
|
|
|
|
user.outbox
|
|
|
|
)
|
|
|
|
.execute(conn)
|
|
|
|
.await
|
|
|
|
.unwrap();
|
|
|
|
}
|
|
|
|
|
2025-04-12 15:16:40 +01:00
|
|
|
async fn create_user(
|
|
|
|
user: &Person,
|
2025-04-25 16:46:47 +01:00
|
|
|
actor: &str,
|
2025-04-12 15:16:40 +01:00
|
|
|
conn: impl sqlx::Executor<'_, Database = Sqlite>,
|
|
|
|
) {
|
2025-04-11 15:47:22 +01:00
|
|
|
// HACK: Allow us to formulate a `user@host` username by assuming the actor is on the same host as the user
|
|
|
|
let url = Url::parse(&actor).unwrap();
|
|
|
|
let host = url.host_str().unwrap();
|
|
|
|
let username = format!("{}@{}", user.name, host);
|
|
|
|
|
|
|
|
let uuid = Uuid::new_v4().to_string();
|
|
|
|
sqlx::query!(
|
|
|
|
r#"
|
|
|
|
INSERT INTO user (id, username, actor_id, display_name)
|
|
|
|
VALUES (?1, ?2, ?3, ?4)
|
|
|
|
ON CONFLICT(actor_id) DO NOTHING;
|
|
|
|
"#,
|
|
|
|
uuid,
|
|
|
|
username,
|
|
|
|
actor,
|
|
|
|
user.preferred_username
|
|
|
|
)
|
|
|
|
.execute(conn)
|
|
|
|
.await
|
|
|
|
.unwrap();
|
|
|
|
}
|
|
|
|
|
2025-04-12 15:16:40 +01:00
|
|
|
async fn create_follow(
|
|
|
|
activity: &activity::FollowActivity,
|
|
|
|
conn: impl sqlx::Executor<'_, Database = Sqlite>,
|
|
|
|
) {
|
2025-04-11 15:47:22 +01:00
|
|
|
sqlx::query!(
|
|
|
|
r#"
|
|
|
|
INSERT INTO follow (id, follower_id, followed_id)
|
|
|
|
VALUES ( ?1, ?2, ?3 )
|
|
|
|
ON CONFLICT(id) DO NOTHING;
|
|
|
|
"#,
|
|
|
|
activity.id,
|
|
|
|
activity.actor,
|
|
|
|
activity.object
|
|
|
|
)
|
|
|
|
.execute(conn)
|
|
|
|
.await
|
|
|
|
.unwrap();
|
|
|
|
}
|
|
|
|
|
2025-04-12 15:16:40 +01:00
|
|
|
async fn handle_follow_activity(
|
2025-04-25 16:46:47 +01:00
|
|
|
followed_account: &str,
|
2025-04-12 15:16:40 +01:00
|
|
|
activity: activity::FollowActivity,
|
|
|
|
http: &HttpClient,
|
|
|
|
mut db: Connection<Db>,
|
|
|
|
) {
|
2025-04-11 15:47:22 +01:00
|
|
|
let user = http
|
|
|
|
.get(&activity.actor)
|
|
|
|
.activity()
|
|
|
|
.send()
|
|
|
|
.await
|
|
|
|
.unwrap()
|
|
|
|
.json::<Person>()
|
|
|
|
.await
|
|
|
|
.unwrap();
|
|
|
|
|
2025-04-25 16:46:47 +01:00
|
|
|
create_actor(&user, &activity.actor, &mut **db).await;
|
|
|
|
create_user(&user, &activity.actor, &mut **db).await;
|
2025-04-11 15:47:22 +01:00
|
|
|
create_follow(&activity, &mut **db).await;
|
|
|
|
|
|
|
|
let follower = ap::User::from_actor_id(&activity.actor, &mut **db).await;
|
|
|
|
let followed = ap::User::from_username(&followed_account, &mut **db).await;
|
|
|
|
let outbox = ap::Outbox::for_user(followed.clone(), http);
|
|
|
|
|
|
|
|
let activity = ap::Activity {
|
|
|
|
id: format!("https://ferri.amy.mov/activities/{}", Uuid::new_v4()),
|
|
|
|
ty: ap::ActivityType::Accept,
|
|
|
|
object: activity.id,
|
|
|
|
..Default::default()
|
|
|
|
};
|
|
|
|
|
|
|
|
let req = ap::OutgoingActivity {
|
|
|
|
signed_by: format!(
|
|
|
|
"https://ferri.amy.mov/users/{}#main-key",
|
|
|
|
followed.username()
|
|
|
|
),
|
|
|
|
req: activity,
|
|
|
|
to: follower.actor().clone(),
|
|
|
|
};
|
|
|
|
|
|
|
|
req.save(&mut **db).await;
|
|
|
|
outbox.post(req).await;
|
|
|
|
}
|
|
|
|
|
2025-04-12 11:27:03 +01:00
|
|
|
async fn handle_like_activity(activity: activity::LikeActivity, mut db: Connection<Db>) {
|
2025-04-25 16:46:47 +01:00
|
|
|
warn!(?activity, "unimplemented like activity");
|
|
|
|
|
2025-04-12 11:27:03 +01:00
|
|
|
let target_post = sqlx::query!("SELECT * FROM post WHERE uri = ?1", activity.object)
|
|
|
|
.fetch_one(&mut **db)
|
2025-04-25 16:46:47 +01:00
|
|
|
.await;
|
|
|
|
|
|
|
|
if let Ok(post) = target_post {
|
|
|
|
warn!(?post, "tried to like post");
|
|
|
|
} else {
|
|
|
|
warn!(post = ?activity.object, "could not find post");
|
|
|
|
}
|
2025-04-12 11:27:03 +01:00
|
|
|
}
|
|
|
|
|
2025-04-26 12:17:32 +01:00
|
|
|
async fn handle_boost_activity(
|
|
|
|
activity: activity::BoostActivity,
|
|
|
|
http: &HttpClient,
|
|
|
|
mut db: Connection<Db>,
|
|
|
|
) {
|
|
|
|
let key_id = "https://ferri.amy.mov/users/amy#main-key";
|
|
|
|
dbg!(&activity);
|
|
|
|
let post = http
|
|
|
|
.get(&activity.object)
|
|
|
|
.activity()
|
|
|
|
.sign(&key_id)
|
|
|
|
.send()
|
|
|
|
.await
|
|
|
|
.unwrap()
|
|
|
|
.json::<Post>()
|
|
|
|
.await
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
dbg!(&post);
|
|
|
|
let attribution = post.attributed_to.unwrap();
|
|
|
|
let post_user = http
|
|
|
|
.get(&attribution)
|
|
|
|
.activity()
|
|
|
|
.sign(&key_id)
|
|
|
|
.send()
|
|
|
|
.await
|
|
|
|
.unwrap()
|
|
|
|
.json::<Person>()
|
|
|
|
.await
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
let user = http
|
|
|
|
.get(&activity.actor)
|
|
|
|
.activity()
|
|
|
|
.sign(&key_id)
|
|
|
|
.send()
|
|
|
|
.await
|
|
|
|
.unwrap()
|
|
|
|
.json::<Person>()
|
|
|
|
.await
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
dbg!(&post_user);
|
|
|
|
|
|
|
|
debug!("creating actor {}", activity.actor);
|
|
|
|
create_actor(&user, &activity.actor, &mut **db).await;
|
|
|
|
|
|
|
|
debug!("creating user {}", activity.actor);
|
|
|
|
create_user(&user, &activity.actor, &mut **db).await;
|
|
|
|
|
|
|
|
debug!("creating actor {}", attribution);
|
|
|
|
create_actor(&post_user, &attribution, &mut **db).await;
|
|
|
|
|
|
|
|
debug!("creating user {}", attribution);
|
|
|
|
create_user(&post_user, &attribution, &mut **db).await;
|
|
|
|
|
|
|
|
let attributed_user = ap::User::from_actor_id(&attribution, &mut **db).await;
|
|
|
|
let actor_user = ap::User::from_actor_id(&activity.actor, &mut **db).await;
|
|
|
|
|
|
|
|
let base_id = ap::new_id();
|
|
|
|
let now = ap::new_ts();
|
|
|
|
|
|
|
|
let reblog_id = ap::new_id();
|
|
|
|
|
|
|
|
let attr_id = attributed_user.id();
|
|
|
|
sqlx::query!("
|
|
|
|
INSERT INTO post (id, uri, user_id, content, created_at)
|
|
|
|
VALUES (?1, ?2, ?3, ?4, ?5)
|
|
|
|
", reblog_id, post.id, attr_id, post.content, post.ts)
|
|
|
|
.execute(&mut **db)
|
|
|
|
.await
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
let uri = format!("https://ferri.amy.mov/users/{}/posts/{}", actor_user.id(), post.id);
|
|
|
|
let user_id = actor_user.id();
|
|
|
|
|
|
|
|
sqlx::query!("
|
|
|
|
INSERT INTO post (id, uri, user_id, content, created_at, boosted_post_id)
|
|
|
|
VALUES (?1, ?2, ?3, ?4, ?5, ?6)
|
|
|
|
", base_id, uri, user_id, "", now, reblog_id)
|
|
|
|
.execute(&mut **db)
|
|
|
|
.await
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2025-04-12 15:16:40 +01:00
|
|
|
async fn handle_create_activity(
|
|
|
|
activity: activity::CreateActivity,
|
|
|
|
http: &HttpClient,
|
|
|
|
mut db: Connection<Db>,
|
|
|
|
) {
|
2025-04-11 15:47:22 +01:00
|
|
|
assert!(&activity.object.ty == "Note");
|
2025-04-25 16:46:47 +01:00
|
|
|
debug!("resolving user {}", activity.actor);
|
|
|
|
|
2025-04-11 15:47:22 +01:00
|
|
|
let user = http
|
|
|
|
.get(&activity.actor)
|
|
|
|
.activity()
|
|
|
|
.send()
|
|
|
|
.await
|
|
|
|
.unwrap()
|
|
|
|
.json::<Person>()
|
|
|
|
.await
|
|
|
|
.unwrap();
|
|
|
|
|
2025-04-25 16:46:47 +01:00
|
|
|
debug!("creating actor {}", activity.actor);
|
|
|
|
create_actor(&user, &activity.actor, &mut **db).await;
|
|
|
|
|
|
|
|
debug!("creating user {}", activity.actor);
|
|
|
|
create_user(&user, &activity.actor, &mut **db).await;
|
2025-04-11 15:47:22 +01:00
|
|
|
|
|
|
|
let user = ap::User::from_actor_id(&activity.actor, &mut **db).await;
|
2025-04-25 16:46:47 +01:00
|
|
|
debug!("user created {:?}", user);
|
2025-04-11 15:47:22 +01:00
|
|
|
|
2025-04-12 11:27:03 +01:00
|
|
|
let user_id = user.id();
|
2025-04-11 15:47:22 +01:00
|
|
|
let now = Local::now().to_rfc3339();
|
|
|
|
let content = activity.object.content.clone();
|
2025-04-12 11:27:03 +01:00
|
|
|
let post_id = Uuid::new_v4().to_string();
|
|
|
|
let uri = activity.id;
|
|
|
|
|
2025-04-25 16:46:47 +01:00
|
|
|
info!(post_id, "creating post");
|
|
|
|
|
2025-04-12 15:16:40 +01:00
|
|
|
sqlx::query!(
|
|
|
|
r#"
|
2025-04-12 11:27:03 +01:00
|
|
|
INSERT INTO post (id, uri, user_id, content, created_at)
|
|
|
|
VALUES (?1, ?2, ?3, ?4, ?5)
|
2025-04-12 15:16:40 +01:00
|
|
|
"#,
|
|
|
|
post_id,
|
|
|
|
uri,
|
|
|
|
user_id,
|
|
|
|
content,
|
|
|
|
now
|
|
|
|
)
|
2025-04-25 16:46:47 +01:00
|
|
|
.execute(&mut **db)
|
|
|
|
.await
|
|
|
|
.unwrap();
|
2025-04-11 15:47:22 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
#[post("/users/<user>/inbox", data = "<body>")]
|
2025-04-25 16:46:47 +01:00
|
|
|
pub async fn inbox(db: Connection<Db>, http: &State<HttpClient>, user: &str, body: String) {
|
2025-04-11 15:47:22 +01:00
|
|
|
let min = serde_json::from_str::<activity::MinimalActivity>(&body).unwrap();
|
2025-04-25 16:46:47 +01:00
|
|
|
let inbox_span = span!(Level::INFO, "inbox-post", user_id = user);
|
|
|
|
|
|
|
|
let _enter = inbox_span.enter();
|
|
|
|
event!(Level::INFO, ?min, "received an activity");
|
|
|
|
|
2025-04-11 15:47:22 +01:00
|
|
|
match min.ty.as_str() {
|
|
|
|
"Delete" => {
|
|
|
|
let activity = serde_json::from_str::<activity::DeleteActivity>(&body).unwrap();
|
|
|
|
handle_delete_activity(activity);
|
|
|
|
}
|
|
|
|
"Follow" => {
|
|
|
|
let activity = serde_json::from_str::<activity::FollowActivity>(&body).unwrap();
|
|
|
|
handle_follow_activity(user, activity, http.inner(), db).await;
|
2025-04-12 15:16:40 +01:00
|
|
|
}
|
2025-04-11 15:47:22 +01:00
|
|
|
"Create" => {
|
|
|
|
let activity = serde_json::from_str::<activity::CreateActivity>(&body).unwrap();
|
|
|
|
handle_create_activity(activity, http.inner(), db).await;
|
2025-04-12 15:16:40 +01:00
|
|
|
}
|
2025-04-12 11:27:03 +01:00
|
|
|
"Like" => {
|
|
|
|
let activity = serde_json::from_str::<activity::LikeActivity>(&body).unwrap();
|
|
|
|
handle_like_activity(activity, db).await;
|
2025-04-12 15:16:40 +01:00
|
|
|
}
|
2025-04-26 12:17:32 +01:00
|
|
|
"Announce" => {
|
|
|
|
let activity = serde_json::from_str::<activity::BoostActivity>(&body).unwrap();
|
|
|
|
handle_boost_activity(activity, http.inner(), db).await;
|
|
|
|
}
|
|
|
|
|
2025-04-25 16:46:47 +01:00
|
|
|
act => {
|
|
|
|
warn!(act, body, "unknown activity");
|
2025-04-11 15:47:22 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-04-25 16:46:47 +01:00
|
|
|
debug!("body in inbox: {}", body);
|
|
|
|
drop(_enter)
|
2025-04-11 15:47:22 +01:00
|
|
|
}
|