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::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<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]
async fn get_article(
Path(title): Path<String>,
Query(query): Query<GetArticle>,
data: Data<DatabaseHandle>,
) -> MyResult<Json<DbArticle>> {
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))
}

View File

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

View File

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

View File

@ -1,5 +1,5 @@
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::{
config::Data,
@ -20,23 +20,6 @@ pub struct DbArticle {
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)]
#[serde(rename_all = "camelCase")]
pub struct Article {
@ -60,7 +43,7 @@ impl Object for DbArticle {
object_id: Url,
data: &Data<Self::DataType>,
) -> Result<Option<Self>, 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)
}

View File

@ -50,7 +50,6 @@ impl DbInstance {
data: &Data<DatabaseHandle>,
) -> 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(())

View File

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

View File

@ -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<Client> = 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::<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();
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<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?)
}