diff --git a/ferri-main/src/types_rewrite/get.rs b/ferri-main/src/types_rewrite/fetch.rs similarity index 85% rename from ferri-main/src/types_rewrite/get.rs rename to ferri-main/src/types_rewrite/fetch.rs index b2ad17d..3e2cb69 100644 --- a/ferri-main/src/types_rewrite/get.rs +++ b/ferri-main/src/types_rewrite/fetch.rs @@ -1,18 +1,24 @@ use crate::types_rewrite::{ObjectUuid, ObjectUri, db}; use sqlx::SqliteConnection; +use thiserror::Error; use tracing::info; use chrono::{NaiveDateTime, DateTime, Utc}; -use crate::types_rewrite::DbError; const SQLITE_TIME_FMT: &'static str = "%Y-%m-%d %H:%M:%S"; +#[derive(Debug, Error)] +pub enum FetchError { + #[error("an unknown error occured when fetching: {0}")] + Unknown(String) +} + fn parse_ts(ts: String) -> Option> { NaiveDateTime::parse_from_str(&ts, SQLITE_TIME_FMT) .ok() .map(|nt| nt.and_utc()) } -pub async fn user_by_id(id: ObjectUuid, conn: &mut SqliteConnection) -> Result { +pub async fn user_by_id(id: ObjectUuid, conn: &mut SqliteConnection) -> Result { info!("fetching user by uuid '{:?}' from the database", id); let record = sqlx::query!(r#" @@ -33,7 +39,7 @@ pub async fn user_by_id(id: ObjectUuid, conn: &mut SqliteConnection) -> Result Result Result Result { - let ts = user.created_at.to_rfc3339(); - sqlx::query!(r#" - INSERT INTO user (id, acct, url, created_at, remote, username, actor_id, display_name) - VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8) - "#, user.id.0, user.acct, user.url, ts, - user.remote, user.username, user.actor.id.0, user.display_name - ) - .execute(conn) - .await - .map_err(|e| DbError::CreationError(e.to_string()))?; - - Ok(user) -} - -pub async fn new_actor(actor: db::Actor, conn: &mut SqliteConnection) -> Result { - sqlx::query!(r#" - INSERT INTO actor (id, inbox, outbox) - VALUES (?1, ?2, ?3) - "#, actor.id.0, actor.inbox, actor.outbox) - .execute(conn) - .await - .map_err(|e| DbError::CreationError(e.to_string()))?; - - Ok(actor) -} diff --git a/ferri-main/src/types_rewrite/mod.rs b/ferri-main/src/types_rewrite/mod.rs index ac55ae3..1d03c12 100644 --- a/ferri-main/src/types_rewrite/mod.rs +++ b/ferri-main/src/types_rewrite/mod.rs @@ -1,19 +1,7 @@ use serde::{Serialize, Deserialize}; -use thiserror::Error; -use std::fmt::Debug; -use uuid::Uuid; pub mod convert; -pub mod get; -pub mod make; - -#[derive(Debug, Error)] -pub enum DbError { - #[error("an unknown error occured when creating: {0}")] - CreationError(String), - #[error("an unknown error occured when fetching: {0}")] - FetchError(String) -} +pub mod fetch; pub const AS_CONTEXT_RAW: &'static str = "https://www.w3.org/ns/activitystreams"; pub fn as_context() -> ObjectContext { @@ -27,18 +15,12 @@ pub enum ObjectContext { Vec(Vec), } -#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] pub struct ObjectUri(pub String); -#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] pub struct ObjectUuid(pub String); -impl ObjectUuid { - pub fn new() -> Self { - Self(Uuid::new_v4().to_string()) - } -} - #[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] pub struct Object { #[serde(rename = "@context")] @@ -50,20 +32,20 @@ pub mod db { use chrono::{DateTime, Utc}; use super::*; - #[derive(Debug, Eq, PartialEq, Clone)] + #[derive(Debug, Eq, PartialEq)] pub struct Actor { pub id: ObjectUri, pub inbox: String, pub outbox: String, } - #[derive(Debug, Eq, PartialEq, Clone)] + #[derive(Debug, Eq, PartialEq)] pub struct UserPosts { // User may have no posts pub last_post_at: Option> } - #[derive(Debug, Eq, PartialEq, Clone)] + #[derive(Debug, Eq, PartialEq)] pub struct User { pub id: ObjectUuid, pub actor: Actor, @@ -171,107 +153,6 @@ pub mod api { pub value: String, pub verified_at: Option, } - - #[derive(Debug, Serialize, Deserialize)] - pub struct Instance { - pub domain: String, - pub title: String, - pub version: String, - pub source_url: String, - pub description: String, - pub thumbnail: Thumbnail, - pub icon: Vec, - pub languages: Vec, - pub configuration: Configuration, - pub registrations: Registrations, - pub contact: Contact, - } - - #[derive(Debug, Serialize, Deserialize)] - pub struct Configuration { - pub urls: Urls, - pub accounts: Accounts, - pub statuses: Statuses, - pub media_attachments: MediaAttachments, - pub polls: Polls, - pub translation: Translation, - } - - #[derive(Debug, Serialize, Deserialize)] - pub struct Accounts { - pub max_featured_tags: i64, - pub max_pinned_statuses: i64, - } - - #[derive(Debug, Serialize, Deserialize)] - pub struct MediaAttachments { - pub supported_mime_types: Vec, - pub description_limit: i64, - pub image_size_limit: i64, - pub image_matrix_limit: i64, - pub video_size_limit: i64, - pub video_frame_rate_limit: i64, - pub video_matrix_limit: i64, - } - - #[derive(Debug, Serialize, Deserialize)] - pub struct Polls { - pub max_options: i64, - pub max_characters_per_option: i64, - pub min_expiration: i64, - pub max_expiration: i64, - } - - #[derive(Debug, Serialize, Deserialize)] - pub struct Statuses { - pub max_characters: i64, - pub max_media_attachments: i64, - pub characters_reserved_per_url: i64, - } - - #[derive(Debug, Serialize, Deserialize)] - pub struct Translation { - pub enabled: bool, - } - - #[derive(Debug, Serialize, Deserialize)] - pub struct Urls { - pub streaming: String, - pub about: String, - pub privacy_policy: String, - pub terms_of_service: String, - } - - #[derive(Debug, Serialize, Deserialize)] - pub struct Contact { - pub email: String, - } - - #[derive(Debug, Serialize, Deserialize)] - pub struct Field { - pub name: String, - pub value: String, - } - - #[derive(Debug, Serialize, Deserialize)] - pub struct Icon { - pub src: String, - pub size: String, - } - - #[derive(Debug, Serialize, Deserialize)] - pub struct Registrations { - pub enabled: bool, - pub approval_required: bool, - pub reason_required: bool, - pub message: Option, - pub min_age: i64, - } - - #[derive(Debug, Serialize, Deserialize)] - pub struct Thumbnail { - pub url: String, - } } #[cfg(test)] diff --git a/ferri-server/src/endpoints/api/instance.rs b/ferri-server/src/endpoints/api/instance.rs index 81c0d37..9fab90f 100644 --- a/ferri-server/src/endpoints/api/instance.rs +++ b/ferri-server/src/endpoints/api/instance.rs @@ -2,7 +2,7 @@ use rocket::{get, serde::json::Json, State}; use crate::Config; -use main::types_rewrite::api::{ +use crate::types::instance::{ Accounts, Configuration, Contact, Instance, MediaAttachments, Polls, Registrations, Statuses, Thumbnail, Translation, Urls, }; diff --git a/ferri-server/src/endpoints/api/timeline.rs b/ferri-server/src/endpoints/api/timeline.rs index c67f982..7748621 100644 --- a/ferri-server/src/endpoints/api/timeline.rs +++ b/ferri-server/src/endpoints/api/timeline.rs @@ -51,8 +51,7 @@ pub async fn home( display_name: String, username: String } - - // FIXME: query! can't cope with this. returns a type error + let posts = sqlx::query_as::<_, Post>( r#" WITH RECURSIVE get_home_timeline_with_boosts( @@ -85,6 +84,8 @@ pub async fn home( .await .unwrap(); + dbg!(&posts); + let mut out = Vec::::new(); for record in posts.iter() { let mut boost: Option> = None; diff --git a/ferri-server/src/endpoints/custom.rs b/ferri-server/src/endpoints/custom.rs index ff5150f..b4a0f7c 100644 --- a/ferri-server/src/endpoints/custom.rs +++ b/ferri-server/src/endpoints/custom.rs @@ -89,11 +89,11 @@ pub async fn test( outbound: &State, mut db: Connection ) -> &'static str { - use main::types_rewrite::{ObjectUuid, get, api}; + use main::types_rewrite::{ObjectUuid, fetch, api}; outbound.0.send(ap::QueueMessage::Heartbeat); let id = ObjectUuid("9b9d497b-2731-435f-a929-e609ca69dac9".to_string()); - let user= dbg!(get::user_by_id(id, &mut **db).await.unwrap()); + let user= dbg!(fetch::user_by_id(id, &mut **db).await.unwrap()); let apu: api::Account = user.into(); dbg!(apu); diff --git a/ferri-server/src/endpoints/inbox.rs b/ferri-server/src/endpoints/inbox.rs index c8b05f1..5d9971a 100644 --- a/ferri-server/src/endpoints/inbox.rs +++ b/ferri-server/src/endpoints/inbox.rs @@ -1,21 +1,19 @@ use chrono::Local; use tracing::Instrument; +use main::ap; use rocket::serde::json::serde_json; use rocket::{State, post}; use rocket_db_pools::Connection; -use sqlx::SqliteConnection; use sqlx::Sqlite; use url::Url; use uuid::Uuid; use tracing::{event, span, Level, debug, warn, info, error}; use crate::http_wrapper::HttpWrapper; -use main::types_rewrite::{make, db, ObjectUuid, ObjectUri, self, ap}; - use crate::{ Db, http::HttpClient, - types::{content::Post, activity}, + types::{Person, content::Post, activity}, }; fn handle_delete_activity(activity: activity::DeleteActivity) { @@ -23,7 +21,7 @@ fn handle_delete_activity(activity: activity::DeleteActivity) { } async fn create_actor( - user: &ap::Person, + user: &Person, actor: &str, conn: impl sqlx::Executor<'_, Database = Sqlite>, ) { @@ -43,7 +41,7 @@ async fn create_actor( } async fn create_user( - user: &ap::Person, + user: &Person, actor: &str, conn: impl sqlx::Executor<'_, Database = Sqlite>, ) { @@ -105,106 +103,36 @@ async fn create_follow( .unwrap(); } -struct RemoteInfo { - acct: String, - web_url: String, - is_remote: bool, -} - -fn get_remote_info(actor_url: &str, person: &ap::Person) -> RemoteInfo { - let url = Url::parse(&actor_url).unwrap(); - let host = url.host_str().unwrap(); - - let (acct, remote) = if host != "ferri.amy.mov" { - (format!("{}@{}", person.preferred_username, host), true) - } else { - (person.preferred_username.clone(), false) - }; - - let url = format!("https://ferri.amy.mov/{}", acct); - - RemoteInfo { - acct: acct.to_string(), - web_url: url, - is_remote: remote - } -} - -async fn resolve_actor<'a>( - actor_url: &str, - http: &HttpWrapper<'a>, - conn: &mut SqliteConnection -) -> Result { - let person = { - let res = http.get_person(&actor_url).await; - if let Err(e) = res { - error!("could not load user {}: {}", actor_url, e.to_string()); - return Err(types_rewrite::DbError::FetchError( - format!("could not load user {}: {}", actor_url, e.to_string()) - )) - } - - res.unwrap() - }; - - let user_id = ObjectUuid::new(); - let remote_info = get_remote_info(actor_url, &person); - - let actor = db::Actor { - id: ObjectUri(actor_url.to_string()), - inbox: person.inbox.clone(), - outbox: person.outbox.clone(), - }; - - info!("creating actor {}", actor_url); - - let actor = make::new_actor(actor.clone(), conn).await.unwrap_or(actor); - - info!("creating user {} ({:#?})", remote_info.acct, person); - - let user = db::User { - id: user_id, - actor, - username: person.name, - display_name: person.preferred_username, - acct: remote_info.acct, - remote: remote_info.is_remote, - url: remote_info.web_url, - created_at: main::ap::now(), - - posts: db::UserPosts { - last_post_at: None - } - }; - - Ok(make::new_user(user.clone(), conn).await.unwrap_or(user)) -} - async fn handle_follow_activity<'a>( followed_account: &str, activity: activity::FollowActivity, http: HttpWrapper<'a>, mut db: Connection, ) { - let actor = resolve_actor(&activity.actor, &http, &mut **db) - .await.unwrap(); - - info!("{:?} follows {}", actor, followed_account); + let user = http.get_person(&activity.actor).await; + if let Err(e) = user { + error!("could not load user {}: {}", activity.actor, e.to_string()); + return + } + let user = user.unwrap(); + + create_actor(&user, &activity.actor, &mut **db).await; + create_user(&user, &activity.actor, &mut **db).await; create_follow(&activity, &mut **db).await; - let follower = main::ap::User::from_actor_id(&activity.actor, &mut **db).await; - let followed = main::ap::User::from_id(&followed_account, &mut **db).await.unwrap(); - let outbox = main::ap::Outbox::for_user(followed.clone(), http.client()); + let follower = ap::User::from_actor_id(&activity.actor, &mut **db).await; + let followed = ap::User::from_id(&followed_account, &mut **db).await.unwrap(); + let outbox = ap::Outbox::for_user(followed.clone(), http.client()); - let activity = main::ap::Activity { + let activity = ap::Activity { id: format!("https://ferri.amy.mov/activities/{}", Uuid::new_v4()), - ty: main::ap::ActivityType::Accept, + ty: ap::ActivityType::Accept, object: activity.id, ..Default::default() }; - let req = main::ap::OutgoingActivity { + let req = ap::OutgoingActivity { signed_by: format!( "https://ferri.amy.mov/users/{}#main-key", followed.username() @@ -289,27 +217,24 @@ async fn handle_boost_activity<'a>( debug!("creating user {}", attribution); create_user(&post_user, &attribution, &mut **db).await; - let attributed_user = main::ap::User::from_actor_id(&attribution, &mut **db).await; - let actor_user = main::ap::User::from_actor_id(&activity.actor, &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 = main::ap::new_id(); - let now = main::ap::new_ts(); + let base_id = ap::new_id(); + let now = ap::new_ts(); - let reblog_id = main::ap::new_id(); + let reblog_id = ap::new_id(); let attr_id = attributed_user.id(); - // HACK: ON CONFLICT is to avoid duplicate remote posts coming in - // check this better in future sqlx::query!(" INSERT INTO post (id, uri, user_id, content, created_at) VALUES (?1, ?2, ?3, ?4, ?5) - ON CONFLICT(uri) DO NOTHING ", 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(), base_id); + let uri = format!("https://ferri.amy.mov/users/{}/posts/{}", actor_user.id(), post.id); let user_id = actor_user.id(); sqlx::query!(" @@ -344,7 +269,7 @@ async fn handle_create_activity<'a>( debug!("creating user {}", activity.actor); create_user(&user, &activity.actor, &mut **db).await; - let user = main::ap::User::from_actor_id(&activity.actor, &mut **db).await; + let user = ap::User::from_actor_id(&activity.actor, &mut **db).await; debug!("user created {:?}", user); let user_id = user.id(); diff --git a/ferri-server/src/http_wrapper.rs b/ferri-server/src/http_wrapper.rs index 6592438..0470b3f 100644 --- a/ferri-server/src/http_wrapper.rs +++ b/ferri-server/src/http_wrapper.rs @@ -1,7 +1,7 @@ use thiserror::Error; use tracing::{error, event, Level}; use crate::http::HttpClient; -use main::types_rewrite::ap; +use crate::types::Person; use std::fmt::Debug; pub struct HttpWrapper<'a> { @@ -65,7 +65,7 @@ impl <'a> HttpWrapper<'a> { Ok(decoded.unwrap()) } - pub async fn get_person(&self, url: &str) -> Result { + pub async fn get_person(&self, url: &str) -> Result { self.get("Person", url).await } } diff --git a/ferri-server/src/types/content.rs b/ferri-server/src/types/content.rs index 0ba38d4..e35c5d9 100644 --- a/ferri-server/src/types/content.rs +++ b/ferri-server/src/types/content.rs @@ -2,7 +2,6 @@ use rocket::serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Debug, Default)] #[serde(crate = "rocket::serde")] -#[deprecated] pub struct Post { // FIXME: This is because Masto sends an array but we don't care #[serde(rename = "@context")] diff --git a/ferri-server/src/types/instance.rs b/ferri-server/src/types/instance.rs new file mode 100644 index 0000000..bfc7c1d --- /dev/null +++ b/ferri-server/src/types/instance.rs @@ -0,0 +1,115 @@ +use rocket::serde::{Deserialize, Serialize}; + +#[derive(Debug, Serialize, Deserialize)] +#[serde(crate = "rocket::serde")] +pub struct Instance { + pub domain: String, + pub title: String, + pub version: String, + pub source_url: String, + pub description: String, + pub thumbnail: Thumbnail, + pub icon: Vec, + pub languages: Vec, + pub configuration: Configuration, + pub registrations: Registrations, + pub contact: Contact, +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(crate = "rocket::serde")] +pub struct Configuration { + pub urls: Urls, + pub accounts: Accounts, + pub statuses: Statuses, + pub media_attachments: MediaAttachments, + pub polls: Polls, + pub translation: Translation, +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(crate = "rocket::serde")] +pub struct Accounts { + pub max_featured_tags: i64, + pub max_pinned_statuses: i64, +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(crate = "rocket::serde")] +pub struct MediaAttachments { + pub supported_mime_types: Vec, + pub description_limit: i64, + pub image_size_limit: i64, + pub image_matrix_limit: i64, + pub video_size_limit: i64, + pub video_frame_rate_limit: i64, + pub video_matrix_limit: i64, +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(crate = "rocket::serde")] +pub struct Polls { + pub max_options: i64, + pub max_characters_per_option: i64, + pub min_expiration: i64, + pub max_expiration: i64, +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(crate = "rocket::serde")] +pub struct Statuses { + pub max_characters: i64, + pub max_media_attachments: i64, + pub characters_reserved_per_url: i64, +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(crate = "rocket::serde")] +pub struct Translation { + pub enabled: bool, +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(crate = "rocket::serde")] +pub struct Urls { + pub streaming: String, + pub about: String, + pub privacy_policy: String, + pub terms_of_service: String, +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(crate = "rocket::serde")] +pub struct Contact { + pub email: String, +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(crate = "rocket::serde")] +pub struct Field { + pub name: String, + pub value: String, +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(crate = "rocket::serde")] +pub struct Icon { + pub src: String, + pub size: String, +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(crate = "rocket::serde")] +pub struct Registrations { + pub enabled: bool, + pub approval_required: bool, + pub reason_required: bool, + pub message: Option, + pub min_age: i64, +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(crate = "rocket::serde")] +pub struct Thumbnail { + pub url: String, +} diff --git a/ferri-server/src/types/mod.rs b/ferri-server/src/types/mod.rs index eb0c3c5..7767a8d 100644 --- a/ferri-server/src/types/mod.rs +++ b/ferri-server/src/types/mod.rs @@ -1,5 +1,6 @@ pub mod activity; pub mod content; +pub mod instance; pub mod oauth; pub mod webfinger; @@ -8,7 +9,6 @@ use rocket::serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Debug)] #[serde(rename_all = "camelCase")] #[serde(crate = "rocket::serde")] -#[deprecated] pub struct UserKey { pub id: String, pub owner: String, @@ -19,7 +19,6 @@ pub struct UserKey { #[derive(Serialize, Deserialize, Debug)] #[serde(rename_all = "camelCase")] #[serde(crate = "rocket::serde")] -#[deprecated] pub struct Person { // FIXME: This is because Masto sends an array but we don't care #[serde(rename = "@context")] diff --git a/ferri-server/src/types/oauth.rs b/ferri-server/src/types/oauth.rs index 7bc1b27..8791fe3 100644 --- a/ferri-server/src/types/oauth.rs +++ b/ferri-server/src/types/oauth.rs @@ -5,7 +5,6 @@ use rocket::{ #[derive(Serialize, Deserialize, Debug, FromForm, Clone)] #[serde(crate = "rocket::serde")] -#[deprecated] pub struct App { pub client_name: String, pub redirect_uris: Vec, @@ -14,7 +13,6 @@ pub struct App { #[derive(Serialize, Deserialize, Debug)] #[serde(crate = "rocket::serde")] -#[deprecated] pub struct CredentialApplication { pub name: String, pub scopes: String, diff --git a/ferri-server/src/types/webfinger.rs b/ferri-server/src/types/webfinger.rs index 71f7d2b..a97c7dd 100644 --- a/ferri-server/src/types/webfinger.rs +++ b/ferri-server/src/types/webfinger.rs @@ -2,7 +2,6 @@ use rocket::serde::{Deserialize, Serialize}; #[derive(Deserialize, Serialize, Debug)] #[serde(crate = "rocket::serde")] -#[deprecated] pub struct Link { pub rel: String, #[serde(rename = "type")] @@ -12,7 +11,6 @@ pub struct Link { #[derive(Deserialize, Serialize, Debug)] #[serde(crate = "rocket::serde")] -#[deprecated] pub struct WebfingerResponse { pub subject: String, pub aliases: Vec,