diff --git a/Cargo.lock b/Cargo.lock index f61e557..93ec660 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -99,6 +99,16 @@ version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" +[[package]] +name = "api_client" +version = "0.1.0" +dependencies = [ + "anyhow", + "once_cell", + "reqwest", + "serde_json", +] + [[package]] name = "async-lock" version = "2.8.0" @@ -861,6 +871,7 @@ dependencies = [ "log", "reqwest", "serde", + "url", ] [[package]] @@ -1164,6 +1175,12 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "http-range-header" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "add0ab9360ddbd88cfeb3bd9574a1d85cfdfa14db10b3e21d3700dbc4328758f" + [[package]] name = "http-signature-normalization" version = "0.7.0" @@ -1290,6 +1307,7 @@ dependencies = [ "serde_json", "sha2", "tokio", + "tower-http", "tracing", "url", "uuid", @@ -2923,6 +2941,24 @@ dependencies = [ "tracing", ] +[[package]] +name = "tower-http" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c5bb1d698276a2443e5ecfabc1008bf15a36c12e6a7176e7bf089ea9131140" +dependencies = [ + "bitflags 2.4.1", + "bytes", + "futures-core", + "futures-util", + "http 0.2.11", + "http-body 0.4.5", + "http-range-header", + "pin-project-lite", + "tower-layer", + "tower-service", +] + [[package]] name = "tower-layer" version = "0.3.2" diff --git a/Cargo.toml b/Cargo.toml index 964a3bb..9678a9d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,4 +7,5 @@ edition = "2021" members = [ "frontend", "backend", + "api_client", ] diff --git a/api_client/Cargo.toml b/api_client/Cargo.toml new file mode 100644 index 0000000..9d64c0c --- /dev/null +++ b/api_client/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "api_client" +version = "0.1.0" +edition = "2021" + +[dependencies] +reqwest = "0.11.22" +serde_json = "1.0.108" +once_cell = "1.18.0" +anyhow = "1.0.75" \ No newline at end of file diff --git a/api_client/src/lib.rs b/api_client/src/lib.rs new file mode 100644 index 0000000..7254c58 --- /dev/null +++ b/api_client/src/lib.rs @@ -0,0 +1,157 @@ +use reqwest::Client;use once_cell::sync::Lazy;use anyhow::anyhow; + +pub static CLIENT: Lazy = Lazy::new(Client::new); + +pub async fn create_article(instance: &IbisInstance, title: String) -> MyResult { + let create_form = CreateArticleData { + title: title.clone(), + }; + let req = CLIENT + .post(format!("http://{}/api/v1/article", &instance.hostname)) + .form(&create_form) + .bearer_auth(&instance.jwt); + let article: ArticleView = handle_json_res(req).await?; + + // create initial edit to ensure that conflicts are generated (there are no conflicts on empty file) + let edit_form = EditArticleData { + article_id: article.article.id, + new_text: TEST_ARTICLE_DEFAULT_TEXT.to_string(), + previous_version_id: article.latest_version, + resolve_conflict_id: None, + }; + edit_article(instance, &edit_form).await +} + +pub async fn get_article(hostname: &str, article_id: i32) -> MyResult { + let get_article = GetArticleData { article_id }; + get_query::(hostname, "article", Some(get_article.clone())).await +} + +pub async fn edit_article_with_conflict( + instance: &IbisInstance, + edit_form: &EditArticleData, +) -> MyResult> { + let req = CLIENT + .patch(format!("http://{}/api/v1/article", instance.hostname)) + .form(edit_form) + .bearer_auth(&instance.jwt); + handle_json_res(req).await +} + +pub async fn get_conflicts(instance: &IbisInstance) -> MyResult> { + let req = CLIENT + .get(format!( + "http://{}/api/v1/edit_conflicts", + &instance.hostname + )) + .bearer_auth(&instance.jwt); + handle_json_res(req).await +} + +pub async fn edit_article( + instance: &IbisInstance, + edit_form: &EditArticleData, +) -> MyResult { + let edit_res = edit_article_with_conflict(instance, edit_form).await?; + assert!(edit_res.is_none()); + get_article(&instance.hostname, edit_form.article_id).await +} + +pub async fn get(hostname: &str, endpoint: &str) -> MyResult + where + T: for<'de> Deserialize<'de>, +{ + get_query(hostname, endpoint, None::).await +} + +pub async fn get_query(hostname: &str, endpoint: &str, query: Option) -> MyResult + where + T: for<'de> Deserialize<'de>, + R: Serialize, +{ + let mut req = CLIENT.get(format!("http://{}/api/v1/{}", hostname, endpoint)); + if let Some(query) = query { + req = req.query(&query); + } + handle_json_res(req).await +} + +pub async fn fork_article( + instance: &IbisInstance, + form: &ForkArticleData, +) -> MyResult { + let req = CLIENT + .post(format!("http://{}/api/v1/article/fork", instance.hostname)) + .form(form) + .bearer_auth(&instance.jwt); + handle_json_res(req).await +} + +pub async fn handle_json_res(req: RequestBuilder) -> MyResult + where + T: for<'de> Deserialize<'de>, +{ + let res = req.send().await?; + let status = res.status(); + let text = res.text().await?; + if status == StatusCode::OK { + Ok(serde_json::from_str(&text).map_err(|e| anyhow!("Json error on {text}: {e}"))?) + } else { + Err(anyhow!("API error: {text}").into()) + } +} + +pub async fn follow_instance(instance: &IbisInstance, follow_instance: &str) -> MyResult<()> { + // fetch beta instance on alpha + let resolve_form = ResolveObject { + id: Url::parse(&format!("http://{}", follow_instance))?, + }; + let instance_resolved: DbInstance = + get_query(&instance.hostname, "resolve_instance", Some(resolve_form)).await?; + + // send follow + let follow_form = FollowInstance { + id: instance_resolved.id, + }; + // cant use post helper because follow doesnt return json + let res = CLIENT + .post(format!( + "http://{}/api/v1/instance/follow", + instance.hostname + )) + .form(&follow_form) + .bearer_auth(&instance.jwt) + .send() + .await?; + if res.status() == StatusCode::OK { + Ok(()) + } else { + Err(anyhow!("API error: {}", res.text().await?).into()) + } +} + +pub async fn register(hostname: &str, username: &str, password: &str) -> MyResult { + let register_form = RegisterUserData { + username: username.to_string(), + password: password.to_string(), + }; + let req = CLIENT + .post(format!("http://{}/api/v1/user/register", hostname)) + .form(®ister_form); + handle_json_res(req).await +} + +pub async fn login( + instance: &IbisInstance, + username: &str, + password: &str, +) -> MyResult { + let login_form = LoginUserData { + username: username.to_string(), + password: password.to_string(), + }; + let req = CLIENT + .post(format!("http://{}/api/v1/user/login", instance.hostname)) + .form(&login_form); + handle_json_res(req).await +} diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 382f256..a06b7c5 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -28,6 +28,7 @@ tokio = { version = "1.34.0", features = ["full"] } tracing = "0.1.40" url = "2.4.1" uuid = { version = "1.6.1", features = ["serde"] } +tower-http = { version = "0.4.0", features = ["cors"] } [dev-dependencies] once_cell = "1.18.0" diff --git a/backend/src/lib.rs b/backend/src/lib.rs index fe255da..8803b1e 100644 --- a/backend/src/lib.rs +++ b/backend/src/lib.rs @@ -17,7 +17,7 @@ use diesel_migrations::EmbeddedMigrations; use diesel_migrations::MigrationHarness; use std::net::ToSocketAddrs; use std::sync::{Arc, Mutex}; -use tracing::info; +use tracing::info;use tower_http::cors::CorsLayer; pub mod api; pub mod database; @@ -64,7 +64,8 @@ pub async fn start(hostname: &str, database_url: &str) -> MyResult<()> { let app = Router::new() .nest("", federation_routes()) .nest("/api/v1", api_routes()) - .layer(FederationMiddleware::new(config)); + .layer(FederationMiddleware::new(config)) + .layer(CorsLayer::permissive()); let addr = hostname .to_socket_addrs()? diff --git a/frontend/Cargo.toml b/frontend/Cargo.toml index 6744b67..c3c2855 100644 --- a/frontend/Cargo.toml +++ b/frontend/Cargo.toml @@ -10,3 +10,4 @@ console_log = "1" console_error_panic_hook = "0.1" log = "0.4" serde = { version = "1", features = ["derive"] } +url = {version = "2.4.1", features = ["serde"] } diff --git a/frontend/dist/frontend-0e306a0cbb659ffacc2bde4bf6be791bdf3c4fd12ec49427513525c99e29cee2c56ae2ebb40ced499f14e919ae9c9bc1.js b/frontend/dist/frontend-570f3815d7fd92194c0bd8caac6e873c37520262d948b899db110969bdd20a82965a58d200fb559dee4285a369e3013a.js similarity index 97% rename from frontend/dist/frontend-0e306a0cbb659ffacc2bde4bf6be791bdf3c4fd12ec49427513525c99e29cee2c56ae2ebb40ced499f14e919ae9c9bc1.js rename to frontend/dist/frontend-570f3815d7fd92194c0bd8caac6e873c37520262d948b899db110969bdd20a82965a58d200fb559dee4285a369e3013a.js index 3d138bf..0295f95 100644 --- a/frontend/dist/frontend-0e306a0cbb659ffacc2bde4bf6be791bdf3c4fd12ec49427513525c99e29cee2c56ae2ebb40ced499f14e919ae9c9bc1.js +++ b/frontend/dist/frontend-570f3815d7fd92194c0bd8caac6e873c37520262d948b899db110969bdd20a82965a58d200fb559dee4285a369e3013a.js @@ -694,16 +694,16 @@ imports.wbg.__wbindgen_memory = function() { const ret = wasm.memory; return addHeapObject(ret); }; -imports.wbg.__wbindgen_closure_wrapper4878 = function(arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 530, __wbg_adapter_28); +imports.wbg.__wbindgen_closure_wrapper5350 = function(arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 595, __wbg_adapter_28); return addHeapObject(ret); }; -imports.wbg.__wbindgen_closure_wrapper4880 = function(arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 528, __wbg_adapter_31); +imports.wbg.__wbindgen_closure_wrapper5352 = function(arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 593, __wbg_adapter_31); return addHeapObject(ret); }; -imports.wbg.__wbindgen_closure_wrapper10314 = function(arg0, arg1, arg2) { - const ret = makeMutClosure(arg0, arg1, 634, __wbg_adapter_34); +imports.wbg.__wbindgen_closure_wrapper10787 = function(arg0, arg1, arg2) { + const ret = makeMutClosure(arg0, arg1, 699, __wbg_adapter_34); return addHeapObject(ret); }; @@ -744,7 +744,7 @@ async function __wbg_init(input) { if (wasm !== undefined) return wasm; if (typeof input === 'undefined') { - input = new URL('frontend-0e306a0cbb659ffacc2bde4bf6be791bdf3c4fd12ec49427513525c99e29cee2c56ae2ebb40ced499f14e919ae9c9bc1_bg.wasm', import.meta.url); + input = new URL('frontend-570f3815d7fd92194c0bd8caac6e873c37520262d948b899db110969bdd20a82965a58d200fb559dee4285a369e3013a_bg.wasm', import.meta.url); } const imports = __wbg_get_imports(); diff --git a/frontend/dist/frontend-0e306a0cbb659ffacc2bde4bf6be791bdf3c4fd12ec49427513525c99e29cee2c56ae2ebb40ced499f14e919ae9c9bc1_bg.wasm b/frontend/dist/frontend-570f3815d7fd92194c0bd8caac6e873c37520262d948b899db110969bdd20a82965a58d200fb559dee4285a369e3013a_bg.wasm similarity index 51% rename from frontend/dist/frontend-0e306a0cbb659ffacc2bde4bf6be791bdf3c4fd12ec49427513525c99e29cee2c56ae2ebb40ced499f14e919ae9c9bc1_bg.wasm rename to frontend/dist/frontend-570f3815d7fd92194c0bd8caac6e873c37520262d948b899db110969bdd20a82965a58d200fb559dee4285a369e3013a_bg.wasm index 7c7cdc9..33f135a 100644 Binary files a/frontend/dist/frontend-0e306a0cbb659ffacc2bde4bf6be791bdf3c4fd12ec49427513525c99e29cee2c56ae2ebb40ced499f14e919ae9c9bc1_bg.wasm and b/frontend/dist/frontend-570f3815d7fd92194c0bd8caac6e873c37520262d948b899db110969bdd20a82965a58d200fb559dee4285a369e3013a_bg.wasm differ diff --git a/frontend/dist/index.html b/frontend/dist/index.html index 72cc1b3..89787dc 100644 --- a/frontend/dist/index.html +++ b/frontend/dist/index.html @@ -1,8 +1,8 @@ @@ -18,8 +18,8 @@ window.wasmBindings = bindings; background-color: lightpink; } - - + +