From 77fba1a082d3fb3fe6fd550587599a11fe7aa4ac Mon Sep 17 00:00:00 2001 From: nullishamy Date: Tue, 29 Apr 2025 22:18:30 +0100 Subject: [PATCH] chore: finish types refactor --- ferri-cli/src/main.rs | 2 +- ferri-main/src/ap/activity.rs | 12 +- ferri-main/src/ap/http.rs | 4 +- ferri-main/src/ap/request_queue.rs | 24 +- ferri-main/src/ap/user.rs | 22 +- ferri-main/src/config/mod.rs | 4 +- ferri-main/src/lib.rs | 2 +- .../src/{types_rewrite => types}/convert.rs | 49 ++-- .../src/{types_rewrite => types}/get.rs | 66 +++--- ferri-main/src/types/make.rs | 45 ++++ .../src/{types_rewrite => types}/mod.rs | 201 +++++++++++++--- ferri-main/src/types_rewrite/make.rs | 30 --- ferri-server/src/endpoints/api/apps.rs | 20 +- ferri-server/src/endpoints/api/instance.rs | 9 +- ferri-server/src/endpoints/api/status.rs | 32 ++- ferri-server/src/endpoints/api/timeline.rs | 25 +- ferri-server/src/endpoints/api/user.rs | 14 +- ferri-server/src/endpoints/custom.rs | 31 +-- ferri-server/src/endpoints/inbox.rs | 221 ++++++++++-------- ferri-server/src/endpoints/oauth.rs | 16 +- ferri-server/src/endpoints/user.rs | 97 ++++---- ferri-server/src/endpoints/well_known.rs | 25 +- ferri-server/src/http_wrapper.rs | 44 ++-- ferri-server/src/lib.rs | 22 +- ferri-server/src/types/activity.rs | 75 ------ ferri-server/src/types/content.rs | 22 -- ferri-server/src/types/mod.rs | 61 ----- ferri-server/src/types/oauth.rs | 24 -- ferri-server/src/types/webfinger.rs | 20 -- 29 files changed, 611 insertions(+), 608 deletions(-) rename ferri-main/src/{types_rewrite => types}/convert.rs (52%) rename ferri-main/src/{types_rewrite => types}/get.rs (61%) create mode 100644 ferri-main/src/types/make.rs rename ferri-main/src/{types_rewrite => types}/mod.rs (65%) delete mode 100644 ferri-main/src/types_rewrite/make.rs delete mode 100644 ferri-server/src/types/activity.rs delete mode 100644 ferri-server/src/types/content.rs delete mode 100644 ferri-server/src/types/mod.rs delete mode 100644 ferri-server/src/types/oauth.rs delete mode 100644 ferri-server/src/types/webfinger.rs diff --git a/ferri-cli/src/main.rs b/ferri-cli/src/main.rs index 9629282..36969c8 100644 --- a/ferri-cli/src/main.rs +++ b/ferri-cli/src/main.rs @@ -47,7 +47,7 @@ async fn main() { ) .execute(&mut *conn) .await - .unwrap(); + .unwrap(); let ts = main::ap::new_ts(); diff --git a/ferri-main/src/ap/activity.rs b/ferri-main/src/ap/activity.rs index 59af769..80f45e7 100644 --- a/ferri-main/src/ap/activity.rs +++ b/ferri-main/src/ap/activity.rs @@ -3,7 +3,7 @@ use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use sqlx::Sqlite; use std::fmt::Debug; -use tracing::{event, Level}; +use tracing::{Level, event}; #[derive(Debug, Clone)] pub enum ActivityType { @@ -104,7 +104,7 @@ impl<'a> Outbox<'a> { pub async fn post(&self, activity: OutgoingActivity) { event!(Level::INFO, ?activity, "activity in outbox"); - + let raw = RawActivity { context: "https://www.w3.org/ns/activitystreams".to_string(), id: activity.req.id.clone(), @@ -127,9 +127,11 @@ impl<'a> Outbox<'a> { .await .unwrap(); - event!(Level::DEBUG, - outbox_res, activity = activity.req.id, - "got response for outbox dispatch" + event!( + Level::DEBUG, + outbox_res, + activity = activity.req.id, + "got response for outbox dispatch" ); } diff --git a/ferri-main/src/ap/http.rs b/ferri-main/src/ap/http.rs index ca0e026..5b8f20b 100644 --- a/ferri-main/src/ap/http.rs +++ b/ferri-main/src/ap/http.rs @@ -12,7 +12,7 @@ use rsa::{ use base64::prelude::*; use chrono::Utc; -use tracing::{event, Level}; +use tracing::{Level, event}; pub struct HttpClient { client: reqwest::Client, @@ -61,7 +61,7 @@ impl RequestBuilder { pub async fn send(self) -> Result { event!(Level::DEBUG, ?self.inner, "sending an http request"); - + self.inner.send().await } diff --git a/ferri-main/src/ap/request_queue.rs b/ferri-main/src/ap/request_queue.rs index 2a230df..fc5ebeb 100644 --- a/ferri-main/src/ap/request_queue.rs +++ b/ferri-main/src/ap/request_queue.rs @@ -1,21 +1,21 @@ use std::sync::mpsc; use std::thread; -use tracing::{info, span, Level}; +use tracing::{Level, info, span}; #[derive(Debug)] pub enum QueueMessage { - Heartbeat + Heartbeat, } pub struct RequestQueue { name: &'static str, send: mpsc::Sender, - recv: mpsc::Receiver + recv: mpsc::Receiver, } #[derive(Clone)] pub struct QueueHandle { - send: mpsc::Sender + send: mpsc::Sender, } impl QueueHandle { @@ -27,33 +27,29 @@ impl QueueHandle { impl RequestQueue { pub fn new(name: &'static str) -> Self { let (send, recv) = mpsc::channel(); - Self { - name, - send, - recv - } + Self { name, send, recv } } pub fn spawn(self) -> QueueHandle { info!("starting up queue '{}'", self.name); - + thread::spawn(move || { info!("queue '{}' up", self.name); let recv = self.recv; - + while let Ok(req) = recv.recv() { // FIXME: When we make this do async things we will need to add tokio and // use proper async handled spans as the enter/drop won't work. // See inbox.rs for how we handle that. let s = span!(Level::INFO, "queue", queue_name = self.name); let _enter = s.enter(); - + info!(?req, "got a message into the queue"); - + drop(_enter); } }); - + QueueHandle { send: self.send } } } diff --git a/ferri-main/src/ap/user.rs b/ferri-main/src/ap/user.rs index 361d0d8..3edd614 100644 --- a/ferri-main/src/ap/user.rs +++ b/ferri-main/src/ap/user.rs @@ -35,7 +35,6 @@ pub struct User { display_name: String, } - #[derive(Error, Debug)] pub enum UserError { #[error("user `{0}` not found")] @@ -67,7 +66,10 @@ impl User { format!("https://ferri.amy.mov/users/{}", self.id()) } - pub async fn from_id(uuid: &str, conn: impl sqlx::Executor<'_, Database = Sqlite>) -> Result { + pub async fn from_id( + uuid: &str, + conn: impl sqlx::Executor<'_, Database = Sqlite>, + ) -> Result { let user = sqlx::query!( r#" SELECT u.*, a.id as "actor_own_id", a.inbox, a.outbox @@ -77,10 +79,10 @@ impl User { "#, uuid ) - .fetch_one(conn) - .await - .map_err(|_| UserError::NotFound(uuid.to_string()))?; - + .fetch_one(conn) + .await + .map_err(|_| UserError::NotFound(uuid.to_string()))?; + Ok(User { id: user.id, username: user.username, @@ -106,10 +108,10 @@ impl User { "#, username ) - .fetch_one(conn) - .await - .unwrap(); - + .fetch_one(conn) + .await + .unwrap(); + User { id: user.id, username: user.username, diff --git a/ferri-main/src/config/mod.rs b/ferri-main/src/config/mod.rs index 79305d2..ebe2532 100644 --- a/ferri-main/src/config/mod.rs +++ b/ferri-main/src/config/mod.rs @@ -14,7 +14,7 @@ impl Config { pub fn host(&self) -> &str { &self.server.host } - + pub fn user_url(&self, user_uuid: &str) -> String { format!("{}/users/{}", self.host(), user_uuid) } @@ -26,7 +26,7 @@ impl Config { pub fn followers_url(&self, user_uuid: &str) -> String { format!("{}/followers", self.user_url(user_uuid)) } - + pub fn following_url(&self, user_uuid: &str) -> String { format!("{}/following", self.user_url(user_uuid)) } diff --git a/ferri-main/src/lib.rs b/ferri-main/src/lib.rs index 48ea0f2..ac5d47b 100644 --- a/ferri-main/src/lib.rs +++ b/ferri-main/src/lib.rs @@ -1,6 +1,6 @@ pub mod ap; pub mod config; -pub mod types_rewrite; +pub mod types; use rand::{Rng, distributions::Alphanumeric}; diff --git a/ferri-main/src/types_rewrite/convert.rs b/ferri-main/src/types/convert.rs similarity index 52% rename from ferri-main/src/types_rewrite/convert.rs rename to ferri-main/src/types/convert.rs index 5daff6b..5453cd4 100644 --- a/ferri-main/src/types_rewrite/convert.rs +++ b/ferri-main/src/types/convert.rs @@ -1,18 +1,18 @@ -use crate::types_rewrite::api; -use crate::types_rewrite::ap; -use crate::types_rewrite::db; +use crate::types::ap; +use crate::types::api; +use crate::types::db; -use crate::types_rewrite::{Object, as_context}; +use crate::types::{Object, ObjectUri, as_context}; impl From for ap::Actor { fn from(val: db::Actor) -> ap::Actor { ap::Actor { obj: Object { context: as_context(), - id: val.id + id: val.id, }, inbox: val.inbox, - outbox: val.outbox + outbox: val.outbox, } } } @@ -22,7 +22,7 @@ impl From for db::Actor { db::Actor { id: val.obj.id, inbox: val.inbox, - outbox: val.outbox + outbox: val.outbox, } } } @@ -34,28 +34,51 @@ impl From for api::Account { username: val.username, acct: val.acct, display_name: val.display_name, - + locked: false, bot: false, - + created_at: val.created_at.to_rfc3339(), attribution_domains: vec![], - + note: "".to_string(), url: val.url, - + avatar: "https://ferri.amy.mov/assets/pfp.png".to_string(), avatar_static: "https://ferri.amy.mov/assets/pfp.png".to_string(), header: "https://ferri.amy.mov/assets/pfp.png".to_string(), header_static: "https://ferri.amy.mov/assets/pfp.png".to_string(), - + followers_count: 0, following_count: 0, statuses_count: 0, last_status_at: val.posts.last_post_at.map(|ts| ts.to_rfc3339()), - + emojis: vec![], fields: vec![], } } } + +impl From for ap::Person { + fn from(val: db::User) -> ap::Person { + ap::Person { + obj: Object { + context: as_context(), + id: ObjectUri(format!("https://ferri.amy.mov/users/{}", val.id.0)), + }, + following: format!("https://ferri.amy.mov/users/{}/following", val.id.0), + followers: format!("https://ferri.amy.mov/users/{}/followers", val.id.0), + summary: format!("ferri {}", val.username), + inbox: format!("https://ferri.amy.mov/users/{}/inbox", val.id.0), + outbox: format!("https://ferri.amy.mov/users/{}/outbox", val.id.0), + preferred_username: val.display_name, + name: val.username, + public_key: Some(ap::UserKey { + id: format!("https://ferri.amy.mov/users/{}#main-key", val.id.0), + owner: format!("https://ferri.amy.mov/users/{}", val.id.0), + public_key: include_str!("../../../public.pem").to_string(), + }), + } + } +} diff --git a/ferri-main/src/types_rewrite/get.rs b/ferri-main/src/types/get.rs similarity index 61% rename from ferri-main/src/types_rewrite/get.rs rename to ferri-main/src/types/get.rs index b2ad17d..d4197e6 100644 --- a/ferri-main/src/types_rewrite/get.rs +++ b/ferri-main/src/types/get.rs @@ -1,10 +1,9 @@ -use crate::types_rewrite::{ObjectUuid, ObjectUri, db}; +use crate::types::{DbError, ObjectUri, ObjectUuid, db}; +use chrono::{DateTime, NaiveDateTime, Utc}; use sqlx::SqliteConnection; 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"; +const SQLITE_TIME_FMT: &str = "%Y-%m-%d %H:%M:%S"; fn parse_ts(ts: String) -> Option> { NaiveDateTime::parse_from_str(&ts, SQLITE_TIME_FMT) @@ -14,8 +13,9 @@ fn parse_ts(ts: String) -> Option> { 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#" + + let record = sqlx::query!( + r#" SELECT u.id as "user_id", u.username, @@ -30,46 +30,54 @@ pub async fn user_by_id(id: ObjectUuid, conn: &mut SqliteConnection) -> 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/mod.rs similarity index 65% rename from ferri-main/src/types_rewrite/mod.rs rename to ferri-main/src/types/mod.rs index ac55ae3..f945f7e 100644 --- a/ferri-main/src/types_rewrite/mod.rs +++ b/ferri-main/src/types/mod.rs @@ -1,6 +1,6 @@ -use serde::{Serialize, Deserialize}; -use thiserror::Error; +use serde::{Deserialize, Serialize}; use std::fmt::Debug; +use thiserror::Error; use uuid::Uuid; pub mod convert; @@ -12,10 +12,10 @@ pub enum DbError { #[error("an unknown error occured when creating: {0}")] CreationError(String), #[error("an unknown error occured when fetching: {0}")] - FetchError(String) + FetchError(String), } -pub const AS_CONTEXT_RAW: &'static str = "https://www.w3.org/ns/activitystreams"; +pub const AS_CONTEXT_RAW: &str = "https://www.w3.org/ns/activitystreams"; pub fn as_context() -> ObjectContext { ObjectContext::Str(AS_CONTEXT_RAW.to_string()) } @@ -33,6 +33,12 @@ pub struct ObjectUri(pub String); #[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] pub struct ObjectUuid(pub String); +impl Default for ObjectUuid { + fn default() -> Self { + Self::new() + } +} + impl ObjectUuid { pub fn new() -> Self { Self(Uuid::new_v4().to_string()) @@ -42,13 +48,13 @@ impl ObjectUuid { #[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] pub struct Object { #[serde(rename = "@context")] - context: ObjectContext, - id: ObjectUri, + pub context: ObjectContext, + pub id: ObjectUri, } pub mod db { - use chrono::{DateTime, Utc}; use super::*; + use chrono::{DateTime, Utc}; #[derive(Debug, Eq, PartialEq, Clone)] pub struct Actor { @@ -60,7 +66,7 @@ pub mod db { #[derive(Debug, Eq, PartialEq, Clone)] pub struct UserPosts { // User may have no posts - pub last_post_at: Option> + pub last_post_at: Option>, } #[derive(Debug, Eq, PartialEq, Clone)] @@ -73,86 +79,210 @@ pub mod db { pub remote: bool, pub url: String, pub created_at: DateTime, - - pub posts: UserPosts + + pub posts: UserPosts, } } pub mod ap { - use serde::{Serialize, Deserialize}; use super::*; - + use serde::{Deserialize, Serialize}; + + #[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] + pub enum ActivityType { + Create, + Note, + Delete, + Accept, + Announce, + Like, + Follow, + } + + #[derive(Serialize, Deserialize, Debug)] + pub struct MinimalActivity { + #[serde(flatten)] + pub obj: Object, + pub ty: ActivityType, + } + + pub type DeleteActivity = BasicActivity; + pub type LikeActivity = BasicActivity; + + #[derive(Serialize, Deserialize, Debug)] + pub struct BasicActivity { + #[serde(flatten)] + pub obj: Object, + + pub object: String, + pub actor: String, + } + + #[derive(Serialize, Deserialize, Debug)] + pub struct CreateActivity { + #[serde(flatten)] + pub obj: Object, + + pub ty: ActivityType, + + pub object: Post, + pub actor: String, + pub to: Vec, + pub cc: Vec, + + #[serde(rename = "published")] + pub ts: String, + } + + #[derive(Serialize, Deserialize, Debug)] + pub struct FollowActivity { + #[serde(flatten)] + pub obj: Object, + + pub ty: ActivityType, + + pub object: String, + pub actor: String, + } + + #[derive(Serialize, Deserialize, Debug)] + pub struct AcceptActivity { + #[serde(flatten)] + pub obj: Object, + + pub ty: ActivityType, + + pub object: String, + pub actor: String, + } + + #[derive(Serialize, Deserialize, Debug)] + pub struct BoostActivity { + #[serde(flatten)] + pub obj: Object, + + pub ty: ActivityType, + + pub actor: String, + pub published: String, + pub to: Vec, + pub cc: Vec, + pub object: String, + } + + #[derive(Serialize, Deserialize, Debug)] + pub struct Post { + #[serde(flatten)] + pub obj: Object, + + pub ty: ActivityType, + + #[serde(rename = "published")] + pub ts: String, + pub content: String, + pub to: Vec, + pub cc: Vec, + + #[serde(rename = "attributedTo")] + pub attributed_to: Option, + } + #[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] pub struct Actor { #[serde(flatten)] pub obj: Object, - + pub inbox: String, pub outbox: String, } #[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] + #[serde(rename_all = "camelCase")] pub struct Person { #[serde(flatten)] pub obj: Object, pub following: String, pub followers: String, - + pub summary: String, pub inbox: String, pub outbox: String, - + pub preferred_username: String, pub name: String, - + pub public_key: Option, } - + #[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] pub struct UserKey { pub id: String, pub owner: String, - + #[serde(rename = "publicKeyPem")] pub public_key: String, } } pub mod api { - use serde::{Serialize, Deserialize}; use super::*; - + use serde::{Deserialize, Serialize}; + // API will not really use actors so treat them as DB actors // until we require specificity pub type Actor = db::Actor; + #[derive(Serialize, Deserialize, Debug)] + pub struct CredentialApplication { + pub name: String, + pub scopes: String, + pub redirect_uris: Vec, + pub client_id: String, + pub client_secret: String, + } + + #[derive(Deserialize, Serialize, Debug)] + pub struct WebfingerLink { + pub rel: String, + #[serde(rename = "type")] + pub ty: Option, + pub href: Option, + } + + #[derive(Deserialize, Serialize, Debug)] + pub struct WebfingerHit { + pub subject: String, + pub aliases: Vec, + pub links: Vec, + } + #[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] pub struct Account { pub id: ObjectUuid, pub username: String, pub acct: String, pub display_name: String, - + pub locked: bool, pub bot: bool, - + pub created_at: String, pub attribution_domains: Vec, - + pub note: String, pub url: String, - + pub avatar: String, pub avatar_static: String, pub header: String, pub header_static: String, - + pub followers_count: i64, pub following_count: i64, pub statuses_count: i64, pub last_status_at: Option, - + pub emojis: Vec, pub fields: Vec, } @@ -277,11 +407,11 @@ pub mod api { #[cfg(test)] mod tests { use super::*; - + #[test] fn ap_actor_to_db() { let domain = "https://example.com"; - + let ap = ap::Actor { obj: Object { context: as_context(), @@ -292,11 +422,14 @@ mod tests { }; let db: db::Actor = ap.into(); - - assert_eq!(db, db::Actor { - id: ObjectUri("https://example.com/users/sample".to_string()), - inbox: "https://example.com/users/sample/inbox".to_string(), - outbox: "https://example.com/users/sample/outbox".to_string(), - }); + + assert_eq!( + db, + db::Actor { + id: ObjectUri("https://example.com/users/sample".to_string()), + inbox: "https://example.com/users/sample/inbox".to_string(), + outbox: "https://example.com/users/sample/outbox".to_string(), + } + ); } } diff --git a/ferri-main/src/types_rewrite/make.rs b/ferri-main/src/types_rewrite/make.rs deleted file mode 100644 index ffaee3a..0000000 --- a/ferri-main/src/types_rewrite/make.rs +++ /dev/null @@ -1,30 +0,0 @@ -use crate::types_rewrite::db; -use sqlx::SqliteConnection; -use crate::types_rewrite::DbError; - -pub async fn new_user(user: db::User, conn: &mut SqliteConnection) -> 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-server/src/endpoints/api/apps.rs b/ferri-server/src/endpoints/api/apps.rs index 1ed5001..7b566d2 100644 --- a/ferri-server/src/endpoints/api/apps.rs +++ b/ferri-server/src/endpoints/api/apps.rs @@ -1,11 +1,23 @@ use rocket::{form::Form, post, serde::json::Json}; use crate::Db; -use crate::types::oauth::{App, CredentialApplication}; +use main::types::api; +use rocket::FromForm; use rocket_db_pools::Connection; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug, FromForm, Clone)] +pub struct OauthApp { + pub client_name: String, + pub redirect_uris: Vec, + pub scopes: String, +} #[post("/apps", data = "")] -pub async fn new_app(app: Form, mut db: Connection) -> Json { +pub async fn new_app( + app: Form, + mut db: Connection, +) -> Json { let secret = main::gen_token(15); // Abort when we encounter a duplicate @@ -22,7 +34,7 @@ pub async fn new_app(app: Form, mut db: Connection) -> Json, mut db: Connection) -> Json) -> Json { +pub async fn instance(helpers: &State) -> Json { + let config = &helpers.config; Json(Instance { domain: config.host().to_string(), title: "Ferri".to_string(), diff --git a/ferri-server/src/endpoints/api/status.rs b/ferri-server/src/endpoints/api/status.rs index ca74a07..8e7694d 100644 --- a/ferri-server/src/endpoints/api/status.rs +++ b/ferri-server/src/endpoints/api/status.rs @@ -3,8 +3,7 @@ use main::ap::{self, http::HttpClient}; use rocket::{ FromForm, State, form::Form, - post, - get, + get, post, serde::{Deserialize, Serialize, json::Json}, }; use rocket_db_pools::Connection; @@ -23,14 +22,14 @@ pub struct Status { #[serde(crate = "rocket::serde")] pub struct StatusContext { ancestors: Vec, - descendants: Vec + descendants: Vec, } #[get("/statuses/<_status>/context")] pub async fn status_context( _status: &str, _user: AuthenticatedUser, - _db: Connection + _db: Connection, ) -> Json { Json(StatusContext { ancestors: vec![], @@ -56,10 +55,13 @@ async fn create_status( post.save(&mut **db).await; - let actor = sqlx::query!("SELECT * FROM actor WHERE id = ?1", "https://fedi.amy.mov/users/9zkygethkdw60001") - .fetch_one(&mut **db) - .await - .unwrap(); + let actor = sqlx::query!( + "SELECT * FROM actor WHERE id = ?1", + "https://fedi.amy.mov/users/9zkygethkdw60001" + ) + .fetch_one(&mut **db) + .await + .unwrap(); let create_id = format!("https://ferri.amy.mov/activities/{}", Uuid::new_v4()); @@ -73,11 +75,7 @@ async fn create_status( ..Default::default() }; - let actor = ap::Actor::from_raw( - actor.id.clone(), - actor.inbox.clone(), - actor.outbox.clone(), - ); + let actor = ap::Actor::from_raw(actor.id.clone(), actor.inbox.clone(), actor.outbox.clone()); let req = ap::OutgoingActivity { req: activity, @@ -134,19 +132,19 @@ async fn create_status( #[post("/statuses", data = "")] pub async fn new_status( db: Connection, - http: &State, + helpers: &State, status: Form, user: AuthenticatedUser, ) -> Json { - Json(create_status(user, db, http.inner(), &status).await) + Json(create_status(user, db, &helpers.http, &status).await) } #[post("/statuses", data = "", rank = 2)] pub async fn new_status_json( db: Connection, - http: &State, + helpers: &State, status: Json, user: AuthenticatedUser, ) -> Json { - Json(create_status(user, db, http.inner(), &status).await) + Json(create_status(user, db, &helpers.http, &status).await) } diff --git a/ferri-server/src/endpoints/api/timeline.rs b/ferri-server/src/endpoints/api/timeline.rs index c67f982..f9b868c 100644 --- a/ferri-server/src/endpoints/api/timeline.rs +++ b/ferri-server/src/endpoints/api/timeline.rs @@ -1,7 +1,6 @@ -use crate::{AuthenticatedUser, Db, endpoints::api::user::CredentialAcount, Config}; +use crate::{AuthenticatedUser, Db, endpoints::api::user::CredentialAcount}; use rocket::{ - State, - get, + State, get, serde::{Deserialize, Serialize, json::Json}, }; use rocket_db_pools::Connection; @@ -36,9 +35,11 @@ pub struct TimelineStatus { #[get("/timelines/home")] pub async fn home( mut db: Connection, - config: &State, + helpers: &State, _user: AuthenticatedUser, ) -> Json> { + let config = &helpers.config; + #[derive(sqlx::FromRow, Debug)] struct Post { is_boost_source: bool, @@ -49,7 +50,7 @@ pub async fn home( created_at: String, boosted_post_id: Option, display_name: String, - username: String + username: String, } // FIXME: query! can't cope with this. returns a type error @@ -78,12 +79,12 @@ pub async fn home( FROM get_home_timeline_with_boosts JOIN post p ON p.id = get_home_timeline_with_boosts.id JOIN user u ON u.id = p.user_id; - "# + "#, ) - .bind("https://ferri.amy.mov/users/9b9d497b-2731-435f-a929-e609ca69dac9") - .fetch_all(&mut **db) - .await - .unwrap(); + .bind("https://ferri.amy.mov/users/9b9d497b-2731-435f-a929-e609ca69dac9") + .fetch_all(&mut **db) + .await + .unwrap(); let mut out = Vec::::new(); for record in posts.iter() { @@ -91,7 +92,7 @@ pub async fn home( if let Some(ref boosted_id) = record.boosted_post_id { let user_uri = config.user_url(&record.user_id); let record = posts.iter().find(|p| &p.post_id == boosted_id).unwrap(); - + boost = Some(Box::new(TimelineStatus { id: record.post_id.clone(), created_at: record.created_at.clone(), @@ -131,7 +132,7 @@ pub async fn home( following_count: 1, statuses_count: 1, last_status_at: "2025-04-10T22:14:34Z".to_string(), - }, + }, })) } diff --git a/ferri-server/src/endpoints/api/user.rs b/ferri-server/src/endpoints/api/user.rs index 38428e2..2c174a4 100644 --- a/ferri-server/src/endpoints/api/user.rs +++ b/ferri-server/src/endpoints/api/user.rs @@ -1,14 +1,14 @@ use main::ap; +use rocket::response::status::NotFound; use rocket::{ State, get, post, serde::{Deserialize, Serialize, json::Json}, }; use rocket_db_pools::Connection; use uuid::Uuid; -use rocket::response::status::NotFound; use crate::timeline::{TimelineAccount, TimelineStatus}; -use crate::{AuthenticatedUser, Db, http::HttpClient}; +use crate::{AuthenticatedUser, Db}; #[derive(Debug, Serialize, Deserialize)] #[serde(crate = "rocket::serde")] @@ -60,17 +60,19 @@ pub async fn verify_credentials() -> Json { #[post("/accounts//follow")] pub async fn new_follow( mut db: Connection, - http: &State, + helpers: &State, uuid: &str, user: AuthenticatedUser, ) -> Result<(), NotFound> { + let http = &helpers.http; + let follower = ap::User::from_actor_id(&user.actor_id, &mut **db).await; - + let followed = ap::User::from_id(uuid, &mut **db) .await .map_err(|e| NotFound(e.to_string()))?; - let outbox = ap::Outbox::for_user(follower.clone(), http.inner()); + let outbox = ap::Outbox::for_user(follower.clone(), http); let activity = ap::Activity { id: format!("https://ferri.amy.mov/activities/{}", Uuid::new_v4()), @@ -87,7 +89,7 @@ pub async fn new_follow( req.save(&mut **db).await; outbox.post(req).await; - + Ok(()) } diff --git a/ferri-server/src/endpoints/custom.rs b/ferri-server/src/endpoints/custom.rs index ff5150f..f730a24 100644 --- a/ferri-server/src/endpoints/custom.rs +++ b/ferri-server/src/endpoints/custom.rs @@ -1,15 +1,9 @@ +use crate::{Db, OutboundQueue}; +use main::types::{ap, api}; use rocket::{State, get, response::status}; use rocket_db_pools::Connection; -use main::ap; -use crate::OutboundQueue; - use uuid::Uuid; -use crate::{ - Db, - types::{self, webfinger}, -}; - #[get("/finger/")] pub async fn finger_account(mut db: Connection, account: &str) -> status::Accepted { // user@host.com @@ -23,7 +17,7 @@ pub async fn finger_account(mut db: Connection, account: &str) -> status::Ac VALUES (?1, ?2, ?3) ON CONFLICT(id) DO NOTHING "#, - user.id, + user.obj.id.0, user.inbox, user.outbox ) @@ -41,7 +35,7 @@ pub async fn finger_account(mut db: Connection, account: &str) -> status::Ac "#, uuid, username, - user.id, + user.obj.id.0, user.preferred_username ) .execute(&mut **db) @@ -51,7 +45,7 @@ pub async fn finger_account(mut db: Connection, account: &str) -> status::Ac status::Accepted(format!("https://ferri.amy.mov/users/{}", uuid)) } -pub async fn resolve_user(acct: &str, host: &str) -> types::Person { +pub async fn resolve_user(acct: &str, host: &str) -> ap::Person { let client = reqwest::Client::new(); let url = format!( "https://{}/.well-known/webfinger?resource=acct:{}", @@ -62,7 +56,7 @@ pub async fn resolve_user(acct: &str, host: &str) -> types::Person { .send() .await .unwrap() - .json::() + .json::() .await .unwrap(); @@ -79,21 +73,18 @@ pub async fn resolve_user(acct: &str, host: &str) -> types::Person { .send() .await .unwrap() - .json::() + .json::() .await .unwrap() } #[get("/test")] -pub async fn test( - outbound: &State, - mut db: Connection -) -> &'static str { - use main::types_rewrite::{ObjectUuid, get, api}; - outbound.0.send(ap::QueueMessage::Heartbeat); +pub async fn test(outbound: &State, mut db: Connection) -> &'static str { + use main::types::{ObjectUuid, api, get}; + outbound.0.send(main::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!(get::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..8145340 100644 --- a/ferri-server/src/endpoints/inbox.rs +++ b/ferri-server/src/endpoints/inbox.rs @@ -1,24 +1,19 @@ +use crate::http_wrapper::HttpWrapper; use chrono::Local; -use tracing::Instrument; use rocket::serde::json::serde_json; use rocket::{State, post}; use rocket_db_pools::Connection; -use sqlx::SqliteConnection; use sqlx::Sqlite; +use sqlx::SqliteConnection; +use tracing::Instrument; +use tracing::{Level, debug, error, event, info, span, warn}; 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; +use main::types::{DbError, ObjectUri, ObjectUuid, ap, db, make}; -use crate::{ - Db, - http::HttpClient, - types::{content::Post, activity}, -}; - -fn handle_delete_activity(activity: activity::DeleteActivity) { +fn handle_delete_activity(activity: ap::DeleteActivity) { warn!(?activity, "unimplemented delete activity"); } @@ -48,10 +43,13 @@ async fn create_user( conn: impl sqlx::Executor<'_, Database = Sqlite>, ) { // 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 url = Url::parse(actor).unwrap(); let host = url.host_str().unwrap(); - info!("creating user '{}'@'{}' ({:#?})", user.preferred_username, host, user); - + info!( + "creating user '{}'@'{}' ({:#?})", + user.preferred_username, host, user + ); + let (acct, remote) = if host != "ferri.amy.mov" { (format!("{}@{}", user.preferred_username, host), true) } else { @@ -87,7 +85,7 @@ async fn create_user( } async fn create_follow( - activity: &activity::FollowActivity, + activity: &ap::FollowActivity, conn: impl sqlx::Executor<'_, Database = Sqlite>, ) { sqlx::query!( @@ -96,7 +94,7 @@ async fn create_follow( VALUES ( ?1, ?2, ?3 ) ON CONFLICT(id) DO NOTHING; "#, - activity.id, + activity.obj.id.0, activity.actor, activity.object ) @@ -108,13 +106,13 @@ async fn create_follow( struct RemoteInfo { acct: String, web_url: String, - is_remote: bool, + is_remote: bool, } fn get_remote_info(actor_url: &str, person: &ap::Person) -> RemoteInfo { - let url = Url::parse(&actor_url).unwrap(); + 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 { @@ -126,27 +124,28 @@ fn get_remote_info(actor_url: &str, person: &ap::Person) -> RemoteInfo { RemoteInfo { acct: acct.to_string(), web_url: url, - is_remote: remote + is_remote: remote, } } async fn resolve_actor<'a>( actor_url: &str, http: &HttpWrapper<'a>, - conn: &mut SqliteConnection -) -> Result { + conn: &mut SqliteConnection, +) -> Result { let person = { - let res = http.get_person(&actor_url).await; + 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()) - )) + return Err(DbError::FetchError(format!( + "could not load user {}: {}", + actor_url, e + ))); } res.unwrap() }; - + let user_id = ObjectUuid::new(); let remote_info = get_remote_info(actor_url, &person); @@ -159,7 +158,7 @@ async fn resolve_actor<'a>( 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 { @@ -172,9 +171,7 @@ async fn resolve_actor<'a>( url: remote_info.web_url, created_at: main::ap::now(), - posts: db::UserPosts { - last_post_at: None - } + posts: db::UserPosts { last_post_at: None }, }; Ok(make::new_user(user.clone(), conn).await.unwrap_or(user)) @@ -182,25 +179,28 @@ async fn resolve_actor<'a>( async fn handle_follow_activity<'a>( followed_account: &str, - activity: activity::FollowActivity, + activity: ap::FollowActivity, http: HttpWrapper<'a>, mut db: Connection, ) { - let actor = resolve_actor(&activity.actor, &http, &mut **db) - .await.unwrap(); + let actor = resolve_actor(&activity.actor, &http, &mut db) + .await + .unwrap(); info!("{:?} follows {}", actor, followed_account); - + 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 followed = main::ap::User::from_id(followed_account, &mut **db) + .await + .unwrap(); let outbox = main::ap::Outbox::for_user(followed.clone(), http.client()); let activity = main::ap::Activity { id: format!("https://ferri.amy.mov/activities/{}", Uuid::new_v4()), ty: main::ap::ActivityType::Accept, - object: activity.id, + object: activity.obj.id.0, ..Default::default() }; @@ -217,9 +217,9 @@ async fn handle_follow_activity<'a>( outbox.post(req).await; } -async fn handle_like_activity(activity: activity::LikeActivity, mut db: Connection) { +async fn handle_like_activity(activity: ap::LikeActivity, mut db: Connection) { warn!(?activity, "unimplemented like activity"); - + let target_post = sqlx::query!("SELECT * FROM post WHERE uri = ?1", activity.object) .fetch_one(&mut **db) .await; @@ -232,48 +232,52 @@ async fn handle_like_activity(activity: activity::LikeActivity, mut db: Connecti } async fn handle_boost_activity<'a>( - activity: activity::BoostActivity, + activity: ap::BoostActivity, http: HttpWrapper<'a>, mut db: Connection, ) { - let key_id = "https://ferri.amy.mov/users/amy#main-key"; + let key_id = "https://ferri.amy.mov/users/9b9d497b-2731-435f-a929-e609ca69dac9#main-key"; dbg!(&activity); let post = http .client() .get(&activity.object) .activity() - .sign(&key_id) + .sign(key_id) .send() .await .unwrap() .text() .await .unwrap(); - + info!("{}", post); - - let post = serde_json::from_str::(&post); + + let post = serde_json::from_str::(&post); if let Err(e) = post { error!(?e, "when decoding post"); - return + return; } - + let post = post.unwrap(); info!("{:#?}", post); let attribution = post.attributed_to.unwrap(); - + let post_user = http.get_person(&attribution).await; if let Err(e) = post_user { - error!("could not load post_user {}: {}", attribution, e.to_string()); - return + error!( + "could not load post_user {}: {}", + attribution, + e.to_string() + ); + return; } let post_user = post_user.unwrap(); let user = http.get_person(&activity.actor).await; if let Err(e) = user { error!("could not load actor {}: {}", activity.actor, e.to_string()); - return + return; } let user = user.unwrap(); @@ -288,52 +292,70 @@ 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 base_id = main::ap::new_id(); let now = main::ap::new_ts(); - + let reblog_id = main::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!(" + 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(); + ", + reblog_id, + post.obj.id.0, + 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(), + base_id + ); let user_id = actor_user.id(); - - sqlx::query!(" + + 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(); - + ", + base_id, + uri, + user_id, + "", + now, + reblog_id + ) + .execute(&mut **db) + .await + .unwrap(); } async fn handle_create_activity<'a>( - activity: activity::CreateActivity, + activity: ap::CreateActivity, http: HttpWrapper<'a>, mut db: Connection, ) { - assert!(&activity.object.ty == "Note"); + assert!(activity.object.ty == ap::ActivityType::Note); debug!("resolving user {}", activity.actor); - + let user = http.get_person(&activity.actor).await; if let Err(e) = user { error!("could not load user {}: {}", activity.actor, e.to_string()); - return + return; } let user = user.unwrap(); @@ -351,7 +373,7 @@ async fn handle_create_activity<'a>( let now = Local::now().to_rfc3339(); let content = activity.object.content.clone(); let post_id = Uuid::new_v4().to_string(); - let uri = activity.id; + let uri = activity.obj.id.0; info!(post_id, "creating post"); @@ -366,52 +388,51 @@ async fn handle_create_activity<'a>( content, now ) - .execute(&mut **db) - .await - .unwrap(); + .execute(&mut **db) + .await + .unwrap(); } #[post("/users//inbox", data = "")] -pub async fn inbox(db: Connection, http: &State, user: &str, body: String) { - let min = serde_json::from_str::(&body).unwrap(); +pub async fn inbox(db: Connection, helpers: &State, user: &str, body: String) { + debug!("body in inbox: {}", body); + + let min = serde_json::from_str::(&body).unwrap(); let inbox_span = span!(Level::INFO, "inbox-post", user_id = user); async move { event!(Level::INFO, ?min, "received an activity"); - - let key_id = "https://ferri.amy.mov/users/amy#main-key"; - let wrapper = HttpWrapper::new(http.inner(), key_id); - - match min.ty.as_str() { - "Delete" => { - let activity = serde_json::from_str::(&body).unwrap(); + + let key_id = "https://ferri.amy.mov/users/9b9d497b-2731-435f-a929-e609ca69dac9#main-key"; + let wrapper = HttpWrapper::new(&helpers.http, key_id); + + match min.ty { + ap::ActivityType::Delete => { + let activity = serde_json::from_str::(&body).unwrap(); handle_delete_activity(activity); } - "Follow" => { - let activity = serde_json::from_str::(&body).unwrap(); + ap::ActivityType::Follow => { + let activity = serde_json::from_str::(&body).unwrap(); handle_follow_activity(user, activity, wrapper, db).await; } - "Create" => { - let activity = serde_json::from_str::(&body).unwrap(); + ap::ActivityType::Create => { + let activity = serde_json::from_str::(&body).unwrap(); handle_create_activity(activity, wrapper, db).await; } - "Like" => { - let activity = serde_json::from_str::(&body).unwrap(); + ap::ActivityType::Like => { + let activity = serde_json::from_str::(&body).unwrap(); handle_like_activity(activity, db).await; } - "Announce" => { - let activity = serde_json::from_str::(&body).unwrap(); + ap::ActivityType::Announce => { + let activity = serde_json::from_str::(&body).unwrap(); handle_boost_activity(activity, wrapper, db).await; } - - act => { - warn!(act, body, "unknown activity"); - } + ap::ActivityType::Note => todo!(), + ap::ActivityType::Accept => todo!(), } - - debug!("body in inbox: {}", body); } // Allow the span to be used inside the async code // https://docs.rs/tracing/latest/tracing/span/struct.EnteredSpan.html#deref-methods-Span - .instrument(inbox_span).await; + .instrument(inbox_span) + .await; } diff --git a/ferri-server/src/endpoints/oauth.rs b/ferri-server/src/endpoints/oauth.rs index 56ed5ce..6d0391e 100644 --- a/ferri-server/src/endpoints/oauth.rs +++ b/ferri-server/src/endpoints/oauth.rs @@ -23,7 +23,7 @@ pub async fn authorize( // This will act as a token for the user, but we will in future say that it expires very shortly // and can only be used for obtaining an access token etc sqlx::query!( - r#" + r#" INSERT INTO auth (token, user_id) VALUES (?1, ?2) "#, @@ -77,15 +77,19 @@ pub struct NewTokenRequest { #[post("/oauth/token", data = "")] pub async fn new_token(req: Form, mut db: Connection) -> Json { - let oauth = sqlx::query!(" + let oauth = sqlx::query!( + " SELECT o.*, a.* FROM oauth o INNER JOIN auth a ON a.token = ?2 WHERE o.access_token = ?1 - ", req.code, req.code) - .fetch_one(&mut **db) - .await - .unwrap(); + ", + req.code, + req.code + ) + .fetch_one(&mut **db) + .await + .unwrap(); let access_token = main::gen_token(15); diff --git a/ferri-server/src/endpoints/user.rs b/ferri-server/src/endpoints/user.rs index ce24ab5..b2d6dfa 100644 --- a/ferri-server/src/endpoints/user.rs +++ b/ferri-server/src/endpoints/user.rs @@ -1,16 +1,23 @@ -use main::ap; -use rocket::{get, http::ContentType, serde::json::Json, State, Responder}; -use rocket_db_pools::Connection; -use rocket::response::Redirect; -use rocket::response::status::NotFound; - -use crate::{ - Config, - Db, - types::{OrderedCollection, Person, UserKey, content}, +use rocket::{ + Responder, State, get, + http::ContentType, + response::{Redirect, status::NotFound}, + serde::json::Json, }; +use rocket_db_pools::Connection; +use serde::{Deserialize, Serialize}; + +use main::types::{Object, ObjectUri, ObjectUuid, ap, as_context, get}; use super::activity_type; +use crate::Db; + +#[derive(Serialize, Deserialize)] +pub struct OrderedCollection { + ty: String, + total_items: i64, + ordered_items: Vec, +} #[get("/users/<_user>/inbox")] pub async fn inbox(_user: String) -> Json { @@ -31,11 +38,14 @@ pub async fn outbox(_user: String) -> Json { } #[get("/users//followers")] -pub async fn followers(mut db: Connection, uuid: &str) -> Result, NotFound> { - let target = ap::User::from_id(uuid, &mut **db) +pub async fn followers( + mut db: Connection, + uuid: &str, +) -> Result, NotFound> { + let target = main::ap::User::from_id(uuid, &mut **db) .await .map_err(|e| NotFound(e.to_string()))?; - + let actor_id = target.actor_id(); let followers = sqlx::query!( @@ -60,11 +70,14 @@ pub async fn followers(mut db: Connection, uuid: &str) -> Result/following")] -pub async fn following(mut db: Connection, uuid: &str) -> Result, NotFound> { - let target = ap::User::from_id(uuid, &mut **db) +pub async fn following( + mut db: Connection, + uuid: &str, +) -> Result, NotFound> { + let target = main::ap::User::from_id(uuid, &mut **db) .await .map_err(|e| NotFound(e.to_string()))?; - + let actor_id = target.actor_id(); let following = sqlx::query!( @@ -91,10 +104,11 @@ pub async fn following(mut db: Connection, uuid: &str) -> Result/posts/")] pub async fn post( mut db: Connection, - config: &State, + helpers: &State, uuid: &str, post: String, -) -> (ContentType, Json) { +) -> (ContentType, Json) { + let config = &helpers.config; let post = sqlx::query!( r#" SELECT * FROM post WHERE id = ?1 @@ -107,11 +121,13 @@ pub async fn post( ( activity_type(), - Json(content::Post { - context: "https://www.w3.org/ns/activitystreams".to_string(), - id: config.post_url(uuid, &post.id), + Json(ap::Post { + obj: Object { + context: as_context(), + id: ObjectUri(config.post_url(uuid, &post.id)), + }, attributed_to: Some(config.user_url(uuid)), - ty: "Note".to_string(), + ty: ap::ActivityType::Note, content: post.content, ts: post.created_at, to: vec![config.followers_url(uuid)], @@ -138,38 +154,17 @@ fn ap_ok(t: T) -> Result, E> { #[get("/users/")] pub async fn user( mut db: Connection, - config: &State, - uuid: &str -) -> Result>, UserFetchError> { + uuid: &str, +) -> Result>, UserFetchError> { if uuid == "amy" { - return Err( - UserFetchError::Moved( - Redirect::permanent("https://ferri.amy.mov/users/9b9d497b-2731-435f-a929-e609ca69dac9") - ) - ) + return Err(UserFetchError::Moved(Redirect::permanent( + "https://ferri.amy.mov/users/9b9d497b-2731-435f-a929-e609ca69dac9", + ))); } - - let user = ap::User::from_id(uuid, &mut **db) + + let user = get::user_by_id(ObjectUuid(uuid.to_string()), &mut db) .await .map_err(|e| UserFetchError::NotFound(NotFound(e.to_string())))?; - let person = Person { - context: "https://www.w3.org/ns/activitystreams".to_string(), - ty: "Person".to_string(), - id: config.user_url(user.id()), - name: user.username().to_string(), - preferred_username: user.display_name().to_string(), - followers: config.followers_url(user.id()), - following: config.following_url(user.id()), - summary: format!("ferri {}", user.username()), - inbox: config.inbox_url(user.id()), - outbox: config.outbox_url(user.id()), - public_key: Some(UserKey { - id: format!("https://ferri.amy.mov/users/{}#main-key", uuid), - owner: config.user_url(user.id()), - public_key: include_str!("../../../public.pem").to_string(), - }), - }; - - ap_ok(Json(person)) + ap_ok(Json(user.into())) } diff --git a/ferri-server/src/endpoints/well_known.rs b/ferri-server/src/endpoints/well_known.rs index fe03322..42d0373 100644 --- a/ferri-server/src/endpoints/well_known.rs +++ b/ferri-server/src/endpoints/well_known.rs @@ -1,14 +1,10 @@ +use crate::Db; use main::ap; -use rocket::{get, serde::json::Json, State}; +use main::types::api; +use rocket::{State, get, serde::json::Json}; use rocket_db_pools::Connection; use tracing::info; -use crate::{ - Config, - Db, - types::webfinger::{Link, WebfingerResponse}, -}; - #[get("/.well-known/host-meta")] pub async fn host_meta() -> &'static str { r#" @@ -21,26 +17,31 @@ pub async fn host_meta() -> &'static str { // https://mastodon.social/.well-known/webfinger?resource=acct:gargron@mastodon.social #[get("/.well-known/webfinger?")] -pub async fn webfinger(mut db: Connection, config: &State, resource: &str) -> Json { +pub async fn webfinger( + mut db: Connection, + helpers: &State, + resource: &str, +) -> Json { + let config = &helpers.config; info!(?resource, "incoming webfinger request"); let acct = resource.strip_prefix("acct:").unwrap(); let (user, _) = acct.split_once("@").unwrap(); let user = ap::User::from_username(user, &mut **db).await; - Json(WebfingerResponse { + Json(api::WebfingerHit { subject: resource.to_string(), aliases: vec![ config.user_url(user.id()), - config.user_web_url(user.username()) + config.user_web_url(user.username()), ], links: vec![ - Link { + api::WebfingerLink { rel: "http://webfinger.net/rel/profile-page".to_string(), ty: Some("text/html".to_string()), href: Some(config.user_web_url(user.username())), }, - Link { + api::WebfingerLink { rel: "self".to_string(), ty: Some("application/activity+json".to_string()), href: Some(config.user_url(user.id())), diff --git a/ferri-server/src/http_wrapper.rs b/ferri-server/src/http_wrapper.rs index 6592438..050ea49 100644 --- a/ferri-server/src/http_wrapper.rs +++ b/ferri-server/src/http_wrapper.rs @@ -1,12 +1,12 @@ -use thiserror::Error; -use tracing::{error, event, Level}; use crate::http::HttpClient; -use main::types_rewrite::ap; +use main::types::ap; use std::fmt::Debug; +use thiserror::Error; +use tracing::{Level, error, event}; pub struct HttpWrapper<'a> { client: &'a HttpClient, - key_id: &'a str + key_id: &'a str, } #[derive(Error, Debug)] @@ -17,23 +17,25 @@ pub enum HttpError { ParseFailure(String, String, String), } -impl <'a> HttpWrapper<'a> { +impl<'a> HttpWrapper<'a> { pub fn new(client: &'a HttpClient, key_id: &'a str) -> HttpWrapper<'a> { - Self { - client, - key_id - } + Self { client, key_id } } pub fn client(&self) -> &'a HttpClient { - &self.client + self.client } - async fn get(&self, ty: &str, url: &str) -> Result { + async fn get( + &self, + ty: &str, + url: &str, + ) -> Result { let ty = ty.to_string(); event!(Level::INFO, url, "loading {}", ty); - - let http_result = self.client + + let http_result = self + .client .get(url) .sign(self.key_id) .activity() @@ -51,15 +53,15 @@ impl <'a> HttpWrapper<'a> { return Err(HttpError::LoadFailure(ty, url.to_string())); } - let decoded = serde_json::from_str::(&raw_body.unwrap()); - + let raw_body = raw_body.unwrap(); + let decoded = serde_json::from_str::(&raw_body); + if let Err(e) = decoded { - error!("could not parse {} for url {}: {:#?}", ty, url, e); - return Err(HttpError::ParseFailure( - ty, - url.to_string(), - e.to_string() - )); + error!( + "could not parse {} for url {}: {:#?} {}", + ty, url, e, &raw_body + ); + return Err(HttpError::ParseFailure(ty, url.to_string(), e.to_string())); } Ok(decoded.unwrap()) diff --git a/ferri-server/src/lib.rs b/ferri-server/src/lib.rs index 520e213..d373a7e 100644 --- a/ferri-server/src/lib.rs +++ b/ferri-server/src/lib.rs @@ -19,7 +19,6 @@ use rocket_db_pools::{Connection, Database, sqlx}; mod cors; mod endpoints; -mod types; mod http_wrapper; #[derive(Database)] @@ -32,9 +31,7 @@ async fn user_profile() -> (ContentType, &'static str) { } #[get("/activities/<_activity>")] -async fn activity_endpoint(_activity: String) { - -} +async fn activity_endpoint(_activity: String) {} #[derive(Debug)] pub struct AuthenticatedUser { @@ -45,8 +42,7 @@ pub struct AuthenticatedUser { } #[derive(Debug)] -pub enum LoginError { -} +pub enum LoginError {} #[rocket::async_trait] impl<'a> FromRequest<'a> for AuthenticatedUser { @@ -90,6 +86,11 @@ impl<'a> FromRequest<'a> for AuthenticatedUser { pub struct OutboundQueue(pub ap::QueueHandle); pub struct InboundQueue(pub ap::QueueHandle); +pub struct Helpers { + http: http::HttpClient, + config: Config, +} + pub fn launch(cfg: Config) -> Rocket { let format = fmt::format() .with_ansi(true) @@ -99,7 +100,7 @@ pub fn launch(cfg: Config) -> Rocket { .with_thread_names(false) .with_source_location(false) .compact(); - + tracing_subscriber::fmt() .event_format(format) .with_writer(std::io::stdout) @@ -111,10 +112,11 @@ pub fn launch(cfg: Config) -> Rocket { let inbound = ap::RequestQueue::new("inbound"); let inbound_handle = inbound.spawn(); - let http_client = http::HttpClient::new(); build() - .manage(cfg) - .manage(http_client) + .manage(Helpers { + config: cfg, + http: http::HttpClient::new(), + }) .manage(OutboundQueue(outbound_handle)) .manage(InboundQueue(inbound_handle)) .attach(Db::init()) diff --git a/ferri-server/src/types/activity.rs b/ferri-server/src/types/activity.rs deleted file mode 100644 index 29b0151..0000000 --- a/ferri-server/src/types/activity.rs +++ /dev/null @@ -1,75 +0,0 @@ -use rocket::serde::{Deserialize, Serialize}; -use crate::types::content::Post; - -#[derive(Serialize, Deserialize, Debug)] -#[serde(crate = "rocket::serde")] -pub struct MinimalActivity { - pub id: String, - #[serde(rename = "type")] - pub ty: String, -} - -pub type DeleteActivity = BasicActivity; -pub type LikeActivity = BasicActivity; - -#[derive(Serialize, Deserialize, Debug)] -#[serde(crate = "rocket::serde")] -pub struct BasicActivity { - pub id: String, - #[serde(rename = "type")] - pub ty: String, - - pub object: String, - pub actor: String, -} - -#[derive(Serialize, Deserialize, Debug)] -#[serde(crate = "rocket::serde")] -pub struct CreateActivity { - pub id: String, - #[serde(rename = "type")] - pub ty: String, - - pub object: Post, - pub actor: String, - pub to: Vec, - pub cc: Vec, - - #[serde(rename = "published")] - pub ts: String, -} - -#[derive(Serialize, Deserialize, Debug)] -#[serde(crate = "rocket::serde")] -pub struct FollowActivity { - pub id: String, - #[serde(rename = "type")] - pub ty: String, - - pub object: String, - pub actor: String, -} - -#[derive(Serialize, Deserialize, Debug)] -#[serde(crate = "rocket::serde")] -pub struct AcceptActivity { - #[serde(rename = "type")] - pub ty: String, - - pub object: String, - pub actor: String, -} - -#[derive(Serialize, Deserialize, Debug)] -#[serde(crate = "rocket::serde")] -pub struct BoostActivity { - #[serde(rename = "type")] - pub ty: String, - - pub id: String, - pub actor: String, - pub published: String, - pub to: Vec, - pub cc: Vec, - pub object: String, -} diff --git a/ferri-server/src/types/content.rs b/ferri-server/src/types/content.rs deleted file mode 100644 index 0ba38d4..0000000 --- a/ferri-server/src/types/content.rs +++ /dev/null @@ -1,22 +0,0 @@ -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")] - #[serde(skip_deserializing)] - pub context: String, - pub id: String, - #[serde(rename = "type")] - pub ty: String, - #[serde(rename = "published")] - pub ts: String, - pub content: String, - pub to: Vec, - pub cc: Vec, - - #[serde(rename = "attributedTo")] - pub attributed_to: Option -} diff --git a/ferri-server/src/types/mod.rs b/ferri-server/src/types/mod.rs deleted file mode 100644 index eb0c3c5..0000000 --- a/ferri-server/src/types/mod.rs +++ /dev/null @@ -1,61 +0,0 @@ -pub mod activity; -pub mod content; -pub mod oauth; -pub mod webfinger; - -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, - #[serde(rename = "publicKeyPem")] - pub public_key: String, -} - -#[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")] - #[serde(skip_deserializing)] - pub context: String, - - pub id: String, - #[serde(rename = "type")] - pub ty: String, - pub following: String, - pub followers: String, - pub summary: String, - pub inbox: String, - pub outbox: String, - pub preferred_username: String, - #[serde(default)] - pub name: String, - pub public_key: Option, -} - -#[derive(Serialize, Deserialize, Debug)] -#[serde(rename_all = "camelCase")] -#[serde(crate = "rocket::serde")] -pub struct Object { - pub id: String, - #[serde(rename = "type")] - pub ty: String, - pub object: String, -} - -#[derive(Serialize, Deserialize, Debug)] -#[serde(rename_all = "camelCase")] -#[serde(crate = "rocket::serde")] -pub struct OrderedCollection { - #[serde(rename = "type")] - pub ty: String, - pub total_items: u64, - pub ordered_items: Vec, -} diff --git a/ferri-server/src/types/oauth.rs b/ferri-server/src/types/oauth.rs deleted file mode 100644 index 7bc1b27..0000000 --- a/ferri-server/src/types/oauth.rs +++ /dev/null @@ -1,24 +0,0 @@ -use rocket::{ - FromForm, - serde::{Deserialize, Serialize}, -}; - -#[derive(Serialize, Deserialize, Debug, FromForm, Clone)] -#[serde(crate = "rocket::serde")] -#[deprecated] -pub struct App { - pub client_name: String, - pub redirect_uris: Vec, - pub scopes: String, -} - -#[derive(Serialize, Deserialize, Debug)] -#[serde(crate = "rocket::serde")] -#[deprecated] -pub struct CredentialApplication { - pub name: String, - pub scopes: String, - pub redirect_uris: Vec, - pub client_id: String, - pub client_secret: String, -} diff --git a/ferri-server/src/types/webfinger.rs b/ferri-server/src/types/webfinger.rs deleted file mode 100644 index 71f7d2b..0000000 --- a/ferri-server/src/types/webfinger.rs +++ /dev/null @@ -1,20 +0,0 @@ -use rocket::serde::{Deserialize, Serialize}; - -#[derive(Deserialize, Serialize, Debug)] -#[serde(crate = "rocket::serde")] -#[deprecated] -pub struct Link { - pub rel: String, - #[serde(rename = "type")] - pub ty: Option, - pub href: Option, -} - -#[derive(Deserialize, Serialize, Debug)] -#[serde(crate = "rocket::serde")] -#[deprecated] -pub struct WebfingerResponse { - pub subject: String, - pub aliases: Vec, - pub links: Vec, -}