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:
parent
b69b0fd30a
commit
59c6c8d46a
7 changed files with 87 additions and 36 deletions
48
src/api.rs
48
src/api.rs
|
@ -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))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(())
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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?)
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue