mirror of
https://github.com/Nutomic/ibis.git
synced 2024-12-23 22:51:23 +00:00
wip
This commit is contained in:
parent
e1c69799b8
commit
166426e48f
13 changed files with 263 additions and 14 deletions
36
Cargo.lock
generated
36
Cargo.lock
generated
|
@ -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"
|
||||
|
|
|
@ -7,4 +7,5 @@ edition = "2021"
|
|||
members = [
|
||||
"frontend",
|
||||
"backend",
|
||||
"api_client",
|
||||
]
|
||||
|
|
10
api_client/Cargo.toml
Normal file
10
api_client/Cargo.toml
Normal file
|
@ -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"
|
157
api_client/src/lib.rs
Normal file
157
api_client/src/lib.rs
Normal file
|
@ -0,0 +1,157 @@
|
|||
use reqwest::Client;use once_cell::sync::Lazy;use anyhow::anyhow;
|
||||
|
||||
pub static CLIENT: Lazy<Client> = Lazy::new(Client::new);
|
||||
|
||||
pub async fn create_article(instance: &IbisInstance, title: String) -> MyResult<ArticleView> {
|
||||
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<ArticleView> {
|
||||
let get_article = GetArticleData { article_id };
|
||||
get_query::<ArticleView, _>(hostname, "article", Some(get_article.clone())).await
|
||||
}
|
||||
|
||||
pub async fn edit_article_with_conflict(
|
||||
instance: &IbisInstance,
|
||||
edit_form: &EditArticleData,
|
||||
) -> MyResult<Option<ApiConflict>> {
|
||||
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<Vec<ApiConflict>> {
|
||||
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<ArticleView> {
|
||||
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<T>(hostname: &str, endpoint: &str) -> MyResult<T>
|
||||
where
|
||||
T: for<'de> Deserialize<'de>,
|
||||
{
|
||||
get_query(hostname, endpoint, None::<i32>).await
|
||||
}
|
||||
|
||||
pub async fn get_query<T, R>(hostname: &str, endpoint: &str, query: Option<R>) -> MyResult<T>
|
||||
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<ArticleView> {
|
||||
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<T>(req: RequestBuilder) -> MyResult<T>
|
||||
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<LoginResponse> {
|
||||
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<LoginResponse> {
|
||||
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
|
||||
}
|
|
@ -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"
|
||||
|
|
|
@ -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()?
|
||||
|
|
|
@ -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"] }
|
||||
|
|
|
@ -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();
|
||||
|
Binary file not shown.
8
frontend/dist/index.html
vendored
8
frontend/dist/index.html
vendored
|
@ -1,8 +1,8 @@
|
|||
<!DOCTYPE html><html><head>
|
||||
|
||||
<script type="module">
|
||||
import init, * as bindings from '/frontend-0e306a0cbb659ffacc2bde4bf6be791bdf3c4fd12ec49427513525c99e29cee2c56ae2ebb40ced499f14e919ae9c9bc1.js';
|
||||
init('/frontend-0e306a0cbb659ffacc2bde4bf6be791bdf3c4fd12ec49427513525c99e29cee2c56ae2ebb40ced499f14e919ae9c9bc1_bg.wasm');
|
||||
import init, * as bindings from '/frontend-570f3815d7fd92194c0bd8caac6e873c37520262d948b899db110969bdd20a82965a58d200fb559dee4285a369e3013a.js';
|
||||
init('/frontend-570f3815d7fd92194c0bd8caac6e873c37520262d948b899db110969bdd20a82965a58d200fb559dee4285a369e3013a_bg.wasm');
|
||||
window.wasmBindings = bindings;
|
||||
|
||||
</script>
|
||||
|
@ -18,8 +18,8 @@ window.wasmBindings = bindings;
|
|||
background-color: lightpink;
|
||||
}
|
||||
</style>
|
||||
<link rel="preload" href="/frontend-0e306a0cbb659ffacc2bde4bf6be791bdf3c4fd12ec49427513525c99e29cee2c56ae2ebb40ced499f14e919ae9c9bc1_bg.wasm" as="fetch" type="application/wasm" crossorigin="anonymous" integrity="sha384-DjBqDLtln_rMK95L9r55G988T9EuxJQnUTUlyZ4pzuLFauLrtAztSZ8U6RmunJvB">
|
||||
<link rel="modulepreload" href="/frontend-0e306a0cbb659ffacc2bde4bf6be791bdf3c4fd12ec49427513525c99e29cee2c56ae2ebb40ced499f14e919ae9c9bc1.js" crossorigin="anonymous" integrity="sha384-HQ2tmiGi8E_nvn6D0oxqS6V_CrDQoH8CwgvZVzZ4Q55AQ5zpe83OGEg-McwNCVnE"></head>
|
||||
<link rel="preload" href="/frontend-570f3815d7fd92194c0bd8caac6e873c37520262d948b899db110969bdd20a82965a58d200fb559dee4285a369e3013a_bg.wasm" as="fetch" type="application/wasm" crossorigin="anonymous" integrity="sha384-Vw84Fdf9khlMC9jKrG6HPDdSAmLZSLiZ2xEJab3SCoKWWljSAPtVne5ChaNp4wE6">
|
||||
<link rel="modulepreload" href="/frontend-570f3815d7fd92194c0bd8caac6e873c37520262d948b899db110969bdd20a82965a58d200fb559dee4285a369e3013a.js" crossorigin="anonymous" integrity="sha384-uvU8cJ-rIWGrY5c2Ms2A0n6dIWdhqLgCPj6u5m_zninPENzyXtefBjknYTthlevK"></head>
|
||||
|
||||
<body>
|
||||
<script>"use strict";
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
use leptos::error::Result;
|
||||
use leptos::*;
|
||||
use log::info;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use url::Url;
|
||||
|
||||
pub fn main() {
|
||||
_ = console_log::init_with_level(log::Level::Debug);
|
||||
|
@ -35,6 +37,35 @@ async fn fetch_cats(count: CatCount) -> Result<Vec<String>> {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: import this from backend somehow
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct DbInstance {
|
||||
pub id: i32,
|
||||
pub ap_id: Url,
|
||||
pub articles_url: Url,
|
||||
pub inbox_url: String,
|
||||
#[serde(skip)]
|
||||
pub public_key: String,
|
||||
#[serde(skip)]
|
||||
pub private_key: Option<String>,
|
||||
pub local: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
||||
pub struct InstanceView {
|
||||
pub instance: DbInstance,
|
||||
pub following: Vec<DbInstance>,
|
||||
}
|
||||
|
||||
async fn fetch_instance(url: &str) -> Result<InstanceView> {
|
||||
let res = reqwest::get(url)
|
||||
.await?
|
||||
.json::<InstanceView>()
|
||||
.await?;
|
||||
info!("{:?}", &res);
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
pub fn fetch_example() -> impl IntoView {
|
||||
let (cat_count, set_cat_count) = create_signal::<CatCount>(0);
|
||||
|
||||
|
@ -43,6 +74,7 @@ pub fn fetch_example() -> impl IntoView {
|
|||
// 2) we're not doing server-side rendering in this example anyway
|
||||
// (during SSR, create_resource will begin loading on the server and resolve on the client)
|
||||
let cats = create_local_resource(move || cat_count.get(), fetch_cats);
|
||||
let instance = create_local_resource(move || "http://localhost:8131/api/v1/instance", fetch_instance);
|
||||
|
||||
let fallback = move |errors: RwSignal<Errors>| {
|
||||
let error_list = move || {
|
||||
|
@ -74,7 +106,14 @@ pub fn fetch_example() -> impl IntoView {
|
|||
})
|
||||
};
|
||||
|
||||
let instance_view = move || {
|
||||
instance.and_then(|data| {
|
||||
view! { <h1>{data.instance.ap_id.to_string()}</h1> }
|
||||
})
|
||||
};
|
||||
|
||||
view! {
|
||||
{instance_view}
|
||||
<div>
|
||||
<label>
|
||||
"How many cats would you like?"
|
||||
|
|
3
rust-toolchain.toml_
Normal file
3
rust-toolchain.toml_
Normal file
|
@ -0,0 +1,3 @@
|
|||
[toolchain]
|
||||
profile = "default"
|
||||
channel = "nightly"
|
|
@ -1,3 +1,3 @@
|
|||
pub async fn main() {
|
||||
pub fn main() {
|
||||
unimplemented!();
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue