1
0
Fork 0
mirror of https://github.com/Nutomic/ibis.git synced 2024-11-26 03:21:09 +00:00

add create article endpoint

This commit is contained in:
Felix Ableitner 2023-11-16 13:38:41 +01:00
parent b69b0fd30a
commit 59c6c8d46a
7 changed files with 87 additions and 36 deletions

View file

@ -1,32 +1,68 @@
use crate::database::DatabaseHandle; use crate::database::DatabaseHandle;
use crate::error::MyResult; use crate::error::MyResult;
use crate::federation::objects::article::DbArticle; use crate::federation::objects::article::DbArticle;
use crate::federation::objects::instance::DbInstance; use crate::federation::objects::instance::DbInstance;
use crate::utils::generate_object_id;
use activitypub_federation::config::Data; use activitypub_federation::config::Data;
use activitypub_federation::fetch::object_id::ObjectId; 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::routing::{get, post};
use axum::{Form, Json, Router}; use axum::{Form, Json, Router};
use axum_macros::debug_handler; use axum_macros::debug_handler;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use url::Url; use url::Url;
pub fn api_routes() -> Router { pub fn api_routes() -> Router {
Router::new() Router::new()
.route("/article/:title", get(get_article)) .route("/article", get(get_article).post(create_article))
.route("/resolve_object", get(resolve_object)) .route("/resolve_object", get(resolve_object))
.route("/instance", get(get_local_instance)) .route("/instance", get(get_local_instance))
.route("/instance/follow", post(follow_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<DatabaseHandle>,
Form(create_article): Form<CreateArticle>,
) -> MyResult<Json<DbArticle>> {
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] #[debug_handler]
async fn get_article( async fn get_article(
Path(title): Path<String>, Query(query): Query<GetArticle>,
data: Data<DatabaseHandle>, data: Data<DatabaseHandle>,
) -> MyResult<Json<DbArticle>> { ) -> MyResult<Json<DbArticle>> {
let instance = data.local_instance(); let articles = data.articles.lock().unwrap();
let article = DbArticle::new(title, "dummy".to_string(), instance.ap_id)?; let article = articles
.iter()
.find(|a| a.title == query.title)
.ok_or(anyhow!("not found"))?
.clone();
Ok(Json(article)) Ok(Json(article))
} }

View file

@ -9,7 +9,7 @@ pub type DatabaseHandle = Arc<Database>;
pub struct Database { pub struct Database {
pub instances: Mutex<Vec<DbInstance>>, pub instances: Mutex<Vec<DbInstance>>,
pub users: Mutex<Vec<DbUser>>, pub users: Mutex<Vec<DbUser>>,
pub posts: Mutex<Vec<DbArticle>>, pub articles: Mutex<Vec<DbArticle>>,
} }
impl Database { impl Database {

View file

@ -30,7 +30,7 @@ pub async fn federation_config(hostname: &str) -> Result<FederationConfig<Databa
let database = Arc::new(Database { let database = Arc::new(Database {
instances: Mutex::new(vec![local_instance]), instances: Mutex::new(vec![local_instance]),
users: Mutex::new(vec![]), users: Mutex::new(vec![]),
posts: Mutex::new(vec![]), articles: Mutex::new(vec![]),
}); });
let config = FederationConfig::builder() let config = FederationConfig::builder()
.domain(hostname) .domain(hostname)

View file

@ -1,5 +1,5 @@
use crate::federation::objects::instance::DbInstance; use crate::federation::objects::instance::DbInstance;
use crate::{database::DatabaseHandle, error::Error, generate_object_id}; use crate::{database::DatabaseHandle, error::Error};
use activitypub_federation::kinds::object::ArticleType; use activitypub_federation::kinds::object::ArticleType;
use activitypub_federation::{ use activitypub_federation::{
config::Data, config::Data,
@ -20,23 +20,6 @@ pub struct DbArticle {
pub local: bool, pub local: bool,
} }
impl DbArticle {
pub fn new(
title: String,
text: String,
attributed_to: ObjectId<DbInstance>,
) -> Result<DbArticle, Error> {
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)] #[derive(Deserialize, Serialize, Debug)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct Article { pub struct Article {
@ -60,7 +43,7 @@ impl Object for DbArticle {
object_id: Url, object_id: Url,
data: &Data<Self::DataType>, data: &Data<Self::DataType>,
) -> Result<Option<Self>, Self::Error> { ) -> Result<Option<Self>, Self::Error> {
let posts = data.posts.lock().unwrap(); let posts = data.articles.lock().unwrap();
let res = posts let res = posts
.clone() .clone()
.into_iter() .into_iter()
@ -98,7 +81,7 @@ impl Object for DbArticle {
local: false, local: false,
}; };
let mut lock = data.posts.lock().unwrap(); let mut lock = data.articles.lock().unwrap();
lock.push(post.clone()); lock.push(post.clone());
Ok(post) Ok(post)
} }

View file

@ -50,7 +50,6 @@ impl DbInstance {
data: &Data<DatabaseHandle>, data: &Data<DatabaseHandle>,
) -> Result<(), Error> { ) -> Result<(), Error> {
let follow = Follow::new(self.ap_id.clone(), other.ap_id.clone())?; let follow = Follow::new(self.ap_id.clone(), other.ap_id.clone())?;
dbg!(&follow);
self.send(follow, vec![other.shared_inbox_or_inbox()], data) self.send(follow, vec![other.shared_inbox_or_inbox()], data)
.await?; .await?;
Ok(()) Ok(())

View file

@ -47,7 +47,6 @@ pub async fn http_post_inbox(
data: Data<DatabaseHandle>, data: Data<DatabaseHandle>,
activity_data: ActivityData, activity_data: ActivityData,
) -> impl IntoResponse { ) -> impl IntoResponse {
dbg!("receive activity");
receive_activity::<WithContext<InboxActivities>, DbInstance, DatabaseHandle>( receive_activity::<WithContext<InboxActivities>, DbInstance, DatabaseHandle>(
activity_data, activity_data,
&data, &data,

View file

@ -1,6 +1,6 @@
extern crate fediwiki; extern crate fediwiki;
use fediwiki::api::{FollowInstance, ResolveObject}; use fediwiki::api::{CreateArticle, FollowInstance, GetArticle, ResolveObject};
use fediwiki::error::MyResult; use fediwiki::error::MyResult;
use fediwiki::federation::objects::article::DbArticle; use fediwiki::federation::objects::article::DbArticle;
use fediwiki::federation::objects::instance::DbInstance; use fediwiki::federation::objects::instance::DbInstance;
@ -28,17 +28,37 @@ static CLIENT: Lazy<Client> = Lazy::new(|| Client::new());
#[tokio::test] #[tokio::test]
#[serial] #[serial]
async fn test_get_article() -> MyResult<()> { async fn test_create_and_read_article() -> MyResult<()> {
setup(); setup();
let hostname = "localhost:8131"; let hostname = "localhost:8131";
let handle = tokio::task::spawn(async { let handle = tokio::task::spawn(async {
start(hostname).await.unwrap(); start(hostname).await.unwrap();
}); });
let title = "Manu_Chao"; // error on nonexistent article
let res: DbArticle = get(hostname, &format!("article/{title}")).await?; let get_article = GetArticle {
assert_eq!(title, res.title); title: "Manu_Chao".to_string(),
assert!(res.local); };
let not_found =
get_query::<DbArticle, _>(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(); handle.abort();
Ok(()) Ok(())
} }
@ -73,6 +93,7 @@ async fn test_follow_instance() -> MyResult<()> {
let follow_instance = FollowInstance { let follow_instance = FollowInstance {
instance_id: beta_instance_resolved.ap_id, instance_id: beta_instance_resolved.ap_id,
}; };
// cant use post helper because follow doesnt return json
CLIENT CLIENT
.post(format!("http://{hostname_alpha}/api/v1/instance/follow")) .post(format!("http://{hostname_alpha}/api/v1/instance/follow"))
.form(&follow_instance) .form(&follow_instance)
@ -110,3 +131,16 @@ where
let alpha_instance: T = res.send().await?.json().await?; let alpha_instance: T = res.send().await?.json().await?;
Ok(alpha_instance) Ok(alpha_instance)
} }
async fn post<T: Serialize, R>(hostname: &str, endpoint: &str, form: &T) -> MyResult<R>
where
R: for<'de> Deserialize<'de>,
{
Ok(CLIENT
.post(format!("http://{}/api/v1/{}", hostname, endpoint))
.form(form)
.send()
.await?
.json()
.await?)
}