diff --git a/src/activities/create_post.rs b/src/activities/create_post.rs index 514f9a6..771bb46 100644 --- a/src/activities/create_post.rs +++ b/src/activities/create_post.rs @@ -6,7 +6,7 @@ use crate::{ person::DbUser, post::{DbPost, Note}, }, - utils::generate_object_id, + utils::generate_random_object_id, }; use activitypub_federation::{ activity_sending::SendActivityTask, @@ -39,7 +39,7 @@ impl CreatePost { to: note.to.clone(), object: note, kind: CreateType::Create, - id: generate_object_id(data.domain())?, + id: generate_random_object_id(data.domain())?, }; let create_with_context = WithContext::new_default(create); let sends = SendActivityTask::prepare( diff --git a/src/activities/follow.rs b/src/activities/follow.rs index b0e0f93..b9986de 100644 --- a/src/activities/follow.rs +++ b/src/activities/follow.rs @@ -14,7 +14,7 @@ use crate::{ database::StateHandle, entities::{follow_relation, post, user}, error, - utils::{generate_follow_accept_id, generate_object_id}, + utils::{generate_follow_accept_id, generate_random_object_id}, DB, }; @@ -39,7 +39,7 @@ impl Follow { actor: local_user.clone(), object: followee.clone(), kind: FollowType::Follow, - id: generate_object_id(data.domain())?, + id: generate_random_object_id(data.domain())?, }; let create_with_context = WithContext::new_default(create); let sends = SendActivityTask::prepare( diff --git a/src/entities/post.rs b/src/entities/post.rs index 97bbd23..0062f05 100644 --- a/src/entities/post.rs +++ b/src/entities/post.rs @@ -15,8 +15,7 @@ pub struct Model { pub created_at: chrono::DateTime, #[sea_orm(column_type = "Timestamp")] pub updated_at: Option>, - #[sea_orm(column_type = "Timestamp")] - pub reblog_id: Option>, + pub reblog_id: Option, pub content_type: String, pub visibility: String, pub reply_id: Option, diff --git a/src/lysand/conversion.rs b/src/lysand/conversion.rs index f016135..fa3d46c 100644 --- a/src/lysand/conversion.rs +++ b/src/lysand/conversion.rs @@ -1,8 +1,8 @@ use activitypub_federation::{fetch::object_id::ObjectId, http_signatures::generate_actor_keypair}; use activitystreams_kinds::public; -use chrono::{DateTime, Utc}; +use chrono::{DateTime, TimeZone, Utc}; use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, Set}; -use anyhow::anyhow; +use anyhow::{anyhow, Ok}; use url::Url; use crate::{database::State, entities::{self, post, prelude, user}, objects::post::Mention, utils::{generate_object_id, generate_user_id}, API_DOMAIN, DB, FEDERATION_CONFIG, LYSAND_DOMAIN}; @@ -27,7 +27,7 @@ pub async fn db_user_from_url(url: Url) -> anyhow::Result if !url.domain().eq(&Some(LYSAND_DOMAIN.as_str())) { return Err(anyhow!("not lysands domain")); } - let user_res = prelude::User::find().filter(entities::user::Column::Url.eq(url.to_string())).one(DB.get().unwrap()).await?; + let user_res: Option = prelude::User::find().filter(entities::user::Column::Url.eq(url.to_string())).one(DB.get().unwrap()).await?; if let Some(user) = user_res { Ok(user) @@ -64,8 +64,8 @@ pub async fn fetch_note_from_url(url: Url) -> anyhow::Result().await?) } -pub async fn receive_lysand_note(note: Note, db_id: String) -> anyhow::Result<()> { - let author: entities::user::Model = db_user_from_url(note.author.clone()).await?; +pub async fn receive_lysand_note(note: Note, db_id: String) -> anyhow::Result { + let lysand_author: entities::user::Model = db_user_from_url(note.author.clone()).await?; let user_res = prelude::User::find_by_id(db_id).one(DB.get().unwrap()).await; if user_res.is_err() { println!("{}", user_res.as_ref().unwrap_err()); @@ -75,23 +75,45 @@ pub async fn receive_lysand_note(note: Note, db_id: String) -> anyhow::Result<() let data = FEDERATION_CONFIG.get().unwrap(); let id: ObjectId = generate_object_id(data.domain(), ¬e.id.to_string())?.into(); let user_id = generate_user_id(data.domain(), &target.id.to_string())?; - let user = fetch_user_from_url(user_id).await?; + let user = fetch_user_from_url(note.author.clone()).await?; let mut tag: Vec = Vec::new(); for l_tag in note.mentions.clone().unwrap_or_default() { - tag.push(Mention { href: l_tag, //todo convert to ap url + tag.push(Mention { href: l_tag, //TODO convert to ap url kind: Default::default(), }) } let to = match note.visibility.clone().unwrap_or(super::objects::VisibilityType::Public) { - super::objects::VisibilityType::Public => vec![public(), Url::parse(&author.followers.unwrap_or_default())?], - super::objects::VisibilityType::Followers => vec![Url::parse(&author.followers.unwrap_or_default())?], + super::objects::VisibilityType::Public => vec![public(), Url::parse(&user.followers.to_string().as_str())?], + super::objects::VisibilityType::Followers => vec![Url::parse(&user.followers.to_string().as_str())?], super::objects::VisibilityType::Direct => note.mentions.unwrap_or_default(), - super::objects::VisibilityType::Unlisted => vec![Url::parse(&author.followers.unwrap_or_default())?], + super::objects::VisibilityType::Unlisted => vec![Url::parse(&user.followers.to_string().as_str())?], }; - let cc = match note.visibility.unwrap_or(super::objects::VisibilityType::Public) { + let cc = match note.visibility.clone().unwrap_or(super::objects::VisibilityType::Public) { super::objects::VisibilityType::Unlisted => Some(vec![public()]), _ => None }; - let reply: Option> = if let Some(rep) = note.replies_to { + let reply: Option> = if let Some(rep) = note.replies_to.clone() { + let note = fetch_note_from_url(rep).await?; + let fake_rep_url = Url::parse(&format!( + "https://{}/lysand/apnote/{}", + API_DOMAIN.to_string(), + ¬e.id.to_string() + ))?; + Some(fake_rep_url.into()) + } else { + None + }; + let reply_string: Option = if let Some(rep) = note.replies_to { + let note = fetch_note_from_url(rep).await?; + let fake_rep_url = Url::parse(&format!( + "https://{}/lysand/apnote/{}", + API_DOMAIN.to_string(), + ¬e.id.to_string() + ))?; + Some(fake_rep_url.into()) + } else { + None + }; + let quote_string: Option = if let Some(rep) = note.quotes.clone() { let note = fetch_note_from_url(rep).await?; let fake_rep_url = Url::parse(&format!( "https://{}/lysand/apnote/{}", @@ -109,12 +131,38 @@ pub async fn receive_lysand_note(note: Note, db_id: String) -> anyhow::Result<() cc, to, tag, - attributed_to: Url::parse(author.url.clone().as_str()).unwrap().into(), + attributed_to: Url::parse(user.uri.clone().as_str()).unwrap().into(), content: option_content_format_text(note.content).await.unwrap_or_default(), in_reply_to: reply }; + + let visibility = match note.visibility.clone().unwrap_or(super::objects::VisibilityType::Public) { + super::objects::VisibilityType::Public => "public", + super::objects::VisibilityType::Followers => "followers", + super::objects::VisibilityType::Direct => "direct", + super::objects::VisibilityType::Unlisted => "unlisted", + }; + + let post = entities::post::ActiveModel { + id: Set(note.id.to_string()), + creator: Set(lysand_author.id.clone()), + content: Set(ap_note.content.clone()), + sensitive: Set(ap_note.sensitive), + created_at: Set(Utc.timestamp_micros(note.created_at.unix_timestamp()).unwrap()), + local: Set(true), + updated_at: Set(Some(Utc::now())), + content_type: Set("Note".to_string()), + visibility: Set(visibility.to_string()), + title: Set(note.subject.clone()), + url: Set(ap_note.id.to_string()), + reply_id: Set(reply_string), + quoting_id: Set(quote_string), + spoiler_text: Set(note.subject), + ..Default::default() + }; + post.insert(DB.get().unwrap()).await?; + Ok(ap_note) + } else { + Err(anyhow!("User not found")) } - - - Ok(()) } \ No newline at end of file diff --git a/src/lysand/funcs.rs b/src/lysand/funcs.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/lysand/http.rs b/src/lysand/http.rs new file mode 100644 index 0000000..229193d --- /dev/null +++ b/src/lysand/http.rs @@ -0,0 +1,24 @@ +use std::os::linux::raw::stat; + +use activitypub_federation::{traits::Object, FEDERATION_CONTENT_TYPE}; +use actix_web::{get, web, HttpResponse}; +use sea_orm::{ColumnTrait, EntityTrait, QueryFilter}; + +use crate::{database::State, entities::{post::{self, Entity}, prelude}, error, Response, DB, FEDERATION_CONFIG}; + +#[get("/apbridge/object/{post}")] +async fn post_manually( + path: web::Path<(String, String)>, + state: web::Data, +) -> actix_web::Result { + let db = DB.get().unwrap(); + + let post = prelude::Post::find() + .filter(post::Column::Id.eq(path.0.as_str())) + .one(db) + .await?; + + let post = post.unwrap(); + + Ok(HttpResponse::Ok().content_type(FEDERATION_CONTENT_TYPE).json(post.into_json(&FEDERATION_CONFIG.get().unwrap().to_request_data()).await?)) +} \ No newline at end of file diff --git a/src/lysand/mod.rs b/src/lysand/mod.rs index 58e6578..ab468b7 100644 --- a/src/lysand/mod.rs +++ b/src/lysand/mod.rs @@ -1,4 +1,6 @@ pub mod objects; pub mod superx; pub mod test; -pub mod conversion; \ No newline at end of file +pub mod conversion; +pub mod funcs; +pub mod http; \ No newline at end of file diff --git a/src/lysand/objects.rs b/src/lysand/objects.rs index bcec067..ad52eb5 100644 --- a/src/lysand/objects.rs +++ b/src/lysand/objects.rs @@ -62,6 +62,7 @@ pub enum CategoryType { } #[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(rename_all = "snake_case")] pub enum VisibilityType { Public, Unlisted, diff --git a/src/lysand/test.rs b/src/lysand/test.rs index a45032b..d56ef63 100644 --- a/src/lysand/test.rs +++ b/src/lysand/test.rs @@ -2,6 +2,19 @@ use crate::lysand::objects::SortAlphabetically; use super::superx::request_client; +#[actix_web::test] +async fn test_user_serial() { + let client = request_client(); + let response = client + .get("https://social.lysand.org/users/018ec082-0ae1-761c-b2c5-22275a611771") + .send() + .await.unwrap(); + let user = super::superx::deserialize_user(response.text().await.unwrap()).await.unwrap(); + let response_outbox = client.get(user.outbox.as_str()).send().await.unwrap(); + let outbox = super::superx::deserialize_outbox(response_outbox.text().await.unwrap()).await.unwrap(); + assert!(outbox.items.len() > 0); +} + pub async fn main() -> anyhow::Result<()> { let client = request_client(); @@ -30,5 +43,13 @@ pub async fn main() -> anyhow::Result<()> { println!("\n\n\nOutbox: "); print!("{:#?}", outbox); + println!("\n\n\nas AP:"); + for item in outbox.items { + let ap_item = super::conversion::receive_lysand_note(item, "https://ap.lysand.org/example".to_string()).await?; + println!("{:#?}", ap_item); + let ap_json = serde_json::to_string_pretty(&SortAlphabetically(&ap_item))?; + println!("{}", ap_json); + } + Ok(()) } diff --git a/src/main.rs b/src/main.rs index 7ef8956..0119c63 100644 --- a/src/main.rs +++ b/src/main.rs @@ -28,7 +28,7 @@ use tokio::signal; use tracing::{info, instrument::WithSubscriber}; use url::Url; -use crate::utils::generate_object_id; +use crate::utils::generate_random_object_id; use crate::{ activities::create_post::CreatePost, database::{Config, State}, @@ -84,7 +84,8 @@ async fn post_manually( href: Url::parse(&target.id)?, kind: Default::default(), }; - let id: ObjectId = generate_object_id(data.domain())?.into(); + // TODO change + let id: ObjectId = generate_random_object_id(data.domain())?.into(); let note = Note { kind: Default::default(), id, @@ -153,10 +154,6 @@ static FEDERATION_CONFIG: OnceLock> = OnceLock::new(); async fn main() -> actix_web::Result<(), anyhow::Error> { env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); - //TODO remove this - lysand::test::main().await?; - return Ok(()); - let ap_id = Url::parse(&format!( "https://{}/{}", API_DOMAIN.to_string(), @@ -246,6 +243,10 @@ async fn main() -> actix_web::Result<(), anyhow::Error> { .keep_alive(KeepAlive::Os) .run(); + //TODO remove this + lysand::test::main().await?; + return Ok(()); + tokio::spawn(http_server); match signal::ctrl_c().await { diff --git a/src/objects/post.rs b/src/objects/post.rs index ee2a83e..9b0ba09 100644 --- a/src/objects/post.rs +++ b/src/objects/post.rs @@ -1,10 +1,5 @@ use crate::{ - activities::create_post::CreatePost, - database::StateHandle, - entities::{post, user}, - error::Error, - objects::person::DbUser, - utils::generate_object_id, + activities::create_post::CreatePost, database::StateHandle, entities::{post, user}, error::Error, lysand::conversion::db_user_from_url, objects::person::DbUser, utils::generate_object_id }; use activitypub_federation::{ config::Data, @@ -14,7 +9,7 @@ use activitypub_federation::{ traits::{Actor, Object}, }; use activitystreams_kinds::link::MentionType; -use sea_orm::{ActiveModelTrait, Set}; +use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, Set}; use serde::{Deserialize, Serialize}; use tracing::info; use url::Url; @@ -57,14 +52,35 @@ impl Object for post::Model { type Error = Error; async fn read_from_id( - _object_id: Url, - _data: &Data, + object_id: Url, + data: &Data, ) -> Result, Self::Error> { + let post = crate::entities::prelude::Post::find() + .filter(post::Column::Id.eq(object_id.to_string())) + .one(data.app_data().database_connection.clone().as_ref()).await; Ok(None) } async fn into_json(self, _data: &Data) -> Result { - todo!() + let creator = db_user_from_url(Url::parse(self.creator.as_str()).unwrap()).await?; + let to = match self.visibility.as_str() { + "public" => vec![public(), Url::parse(creator.followers.unwrap().as_str()).unwrap()], + "followers" => vec![Url::parse(creator.followers.unwrap().as_str()).unwrap()], + "direct" => vec![], //TODO: implement this + "unlisted" => vec![Url::parse(creator.followers.unwrap().as_str()).unwrap(), public()], + _ => vec![public()], + }; + Ok(Note { + kind: Default::default(), + id: Url::parse(self.url.as_str()).unwrap().into(), + attributed_to: Url::parse(self.creator.as_str()).unwrap().into(), + to: to.clone(), + content: self.content, + in_reply_to: None, + tag: vec![], + sensitive: self.sensitive, + cc: Some(to), + }) } async fn verify(