diff --git a/src/api.rs b/src/api.rs index de8fed8..ae5c646 100644 --- a/src/api.rs +++ b/src/api.rs @@ -1,32 +1,68 @@ use crate::database::DatabaseHandle; + use crate::error::MyResult; use crate::federation::objects::article::DbArticle; use crate::federation::objects::instance::DbInstance; +use crate::utils::generate_object_id; use activitypub_federation::config::Data; use activitypub_federation::fetch::object_id::ObjectId; -use axum::extract::{Path, Query}; +use anyhow::anyhow; +use axum::extract::Query; use axum::routing::{get, post}; use axum::{Form, Json, Router}; use axum_macros::debug_handler; use serde::{Deserialize, Serialize}; - use url::Url; pub fn api_routes() -> Router { Router::new() - .route("/article/:title", get(get_article)) + .route("/article", get(get_article).post(create_article)) .route("/resolve_object", get(resolve_object)) .route("/instance", get(get_local_instance)) .route("/instance/follow", post(follow_instance)) } +#[derive(Deserialize, Serialize)] +pub struct CreateArticle { + pub title: String, + pub text: String, +} + +#[debug_handler] +async fn create_article( + data: Data, + Form(create_article): Form, +) -> MyResult> { + let local_instance_id = data.local_instance().ap_id; + let ap_id = generate_object_id(local_instance_id.inner())?.into(); + let article = DbArticle { + title: create_article.title, + text: create_article.text, + ap_id, + instance: local_instance_id, + local: true, + }; + let mut articles = data.articles.lock().unwrap(); + articles.push(article.clone()); + Ok(Json(article)) +} + +#[derive(Deserialize, Serialize, Clone)] +pub struct GetArticle { + pub title: String, +} + #[debug_handler] async fn get_article( - Path(title): Path, + Query(query): Query, data: Data, ) -> MyResult> { - let instance = data.local_instance(); - let article = DbArticle::new(title, "dummy".to_string(), instance.ap_id)?; + let articles = data.articles.lock().unwrap(); + let article = articles + .iter() + .find(|a| a.title == query.title) + .ok_or(anyhow!("not found"))? + .clone(); Ok(Json(article)) } diff --git a/src/database.rs b/src/database.rs index 8a522db..d4cf82a 100644 --- a/src/database.rs +++ b/src/database.rs @@ -9,7 +9,7 @@ pub type DatabaseHandle = Arc; pub struct Database { pub instances: Mutex>, pub users: Mutex>, - pub posts: Mutex>, + pub articles: Mutex>, } impl Database { diff --git a/src/federation/mod.rs b/src/federation/mod.rs index 2dc6805..e8b29d9 100644 --- a/src/federation/mod.rs +++ b/src/federation/mod.rs @@ -30,7 +30,7 @@ pub async fn federation_config(hostname: &str) -> Result, - ) -> Result { - let ap_id = generate_object_id(attributed_to.inner())?.into(); - Ok(DbArticle { - title, - text, - ap_id, - instance: attributed_to, - local: true, - }) - } -} - #[derive(Deserialize, Serialize, Debug)] #[serde(rename_all = "camelCase")] pub struct Article { @@ -60,7 +43,7 @@ impl Object for DbArticle { object_id: Url, data: &Data, ) -> Result, Self::Error> { - let posts = data.posts.lock().unwrap(); + let posts = data.articles.lock().unwrap(); let res = posts .clone() .into_iter() @@ -98,7 +81,7 @@ impl Object for DbArticle { local: false, }; - let mut lock = data.posts.lock().unwrap(); + let mut lock = data.articles.lock().unwrap(); lock.push(post.clone()); Ok(post) } diff --git a/src/federation/objects/instance.rs b/src/federation/objects/instance.rs index cf30a38..98b2b6e 100644 --- a/src/federation/objects/instance.rs +++ b/src/federation/objects/instance.rs @@ -50,7 +50,6 @@ impl DbInstance { data: &Data, ) -> Result<(), Error> { let follow = Follow::new(self.ap_id.clone(), other.ap_id.clone())?; - dbg!(&follow); self.send(follow, vec![other.shared_inbox_or_inbox()], data) .await?; Ok(()) diff --git a/src/federation/routes.rs b/src/federation/routes.rs index 77b120d..ca5a9e3 100644 --- a/src/federation/routes.rs +++ b/src/federation/routes.rs @@ -47,7 +47,6 @@ pub async fn http_post_inbox( data: Data, activity_data: ActivityData, ) -> impl IntoResponse { - dbg!("receive activity"); receive_activity::, DbInstance, DatabaseHandle>( activity_data, &data, diff --git a/tests/test.rs b/tests/test.rs index 21e9b65..b9739bb 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -1,6 +1,6 @@ extern crate fediwiki; -use fediwiki::api::{FollowInstance, ResolveObject}; +use fediwiki::api::{CreateArticle, FollowInstance, GetArticle, ResolveObject}; use fediwiki::error::MyResult; use fediwiki::federation::objects::article::DbArticle; use fediwiki::federation::objects::instance::DbInstance; @@ -28,17 +28,37 @@ static CLIENT: Lazy = Lazy::new(|| Client::new()); #[tokio::test] #[serial] -async fn test_get_article() -> MyResult<()> { +async fn test_create_and_read_article() -> MyResult<()> { setup(); let hostname = "localhost:8131"; let handle = tokio::task::spawn(async { start(hostname).await.unwrap(); }); - let title = "Manu_Chao"; - let res: DbArticle = get(hostname, &format!("article/{title}")).await?; - assert_eq!(title, res.title); - assert!(res.local); + // error on nonexistent article + let get_article = GetArticle { + title: "Manu_Chao".to_string(), + }; + let not_found = + get_query::(hostname, &format!("article"), Some(get_article.clone())).await; + assert!(not_found.is_err()); + + // create article + let create_article = CreateArticle { + title: get_article.title.to_string(), + text: "Lorem ipsum".to_string(), + }; + let create_res: DbArticle = post(hostname, "article", &create_article).await?; + assert_eq!(create_article.title, create_res.title); + assert!(create_res.local); + + // now article can be read + let get_res: DbArticle = + get_query(hostname, &format!("article"), Some(get_article.clone())).await?; + assert_eq!(create_article.title, get_res.title); + assert_eq!(create_article.text, get_res.text); + assert!(get_res.local); + handle.abort(); Ok(()) } @@ -73,6 +93,7 @@ async fn test_follow_instance() -> MyResult<()> { let follow_instance = FollowInstance { instance_id: beta_instance_resolved.ap_id, }; + // cant use post helper because follow doesnt return json CLIENT .post(format!("http://{hostname_alpha}/api/v1/instance/follow")) .form(&follow_instance) @@ -110,3 +131,16 @@ where let alpha_instance: T = res.send().await?.json().await?; Ok(alpha_instance) } + +async fn post(hostname: &str, endpoint: &str, form: &T) -> MyResult +where + R: for<'de> Deserialize<'de>, +{ + Ok(CLIENT + .post(format!("http://{}/api/v1/{}", hostname, endpoint)) + .form(form) + .send() + .await? + .json() + .await?) +}