mirror of
https://github.com/Nutomic/ibis.git
synced 2024-11-29 10:01:10 +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"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
|
checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "api_client"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"once_cell",
|
||||||
|
"reqwest",
|
||||||
|
"serde_json",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-lock"
|
name = "async-lock"
|
||||||
version = "2.8.0"
|
version = "2.8.0"
|
||||||
|
@ -861,6 +871,7 @@ dependencies = [
|
||||||
"log",
|
"log",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
|
"url",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1164,6 +1175,12 @@ dependencies = [
|
||||||
"pin-project-lite",
|
"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]]
|
[[package]]
|
||||||
name = "http-signature-normalization"
|
name = "http-signature-normalization"
|
||||||
version = "0.7.0"
|
version = "0.7.0"
|
||||||
|
@ -1290,6 +1307,7 @@ dependencies = [
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"sha2",
|
"sha2",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"tower-http",
|
||||||
"tracing",
|
"tracing",
|
||||||
"url",
|
"url",
|
||||||
"uuid",
|
"uuid",
|
||||||
|
@ -2923,6 +2941,24 @@ dependencies = [
|
||||||
"tracing",
|
"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]]
|
[[package]]
|
||||||
name = "tower-layer"
|
name = "tower-layer"
|
||||||
version = "0.3.2"
|
version = "0.3.2"
|
||||||
|
|
|
@ -7,4 +7,5 @@ edition = "2021"
|
||||||
members = [
|
members = [
|
||||||
"frontend",
|
"frontend",
|
||||||
"backend",
|
"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"
|
tracing = "0.1.40"
|
||||||
url = "2.4.1"
|
url = "2.4.1"
|
||||||
uuid = { version = "1.6.1", features = ["serde"] }
|
uuid = { version = "1.6.1", features = ["serde"] }
|
||||||
|
tower-http = { version = "0.4.0", features = ["cors"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
once_cell = "1.18.0"
|
once_cell = "1.18.0"
|
||||||
|
|
|
@ -17,7 +17,7 @@ use diesel_migrations::EmbeddedMigrations;
|
||||||
use diesel_migrations::MigrationHarness;
|
use diesel_migrations::MigrationHarness;
|
||||||
use std::net::ToSocketAddrs;
|
use std::net::ToSocketAddrs;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use tracing::info;
|
use tracing::info;use tower_http::cors::CorsLayer;
|
||||||
|
|
||||||
pub mod api;
|
pub mod api;
|
||||||
pub mod database;
|
pub mod database;
|
||||||
|
@ -64,7 +64,8 @@ pub async fn start(hostname: &str, database_url: &str) -> MyResult<()> {
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
.nest("", federation_routes())
|
.nest("", federation_routes())
|
||||||
.nest("/api/v1", api_routes())
|
.nest("/api/v1", api_routes())
|
||||||
.layer(FederationMiddleware::new(config));
|
.layer(FederationMiddleware::new(config))
|
||||||
|
.layer(CorsLayer::permissive());
|
||||||
|
|
||||||
let addr = hostname
|
let addr = hostname
|
||||||
.to_socket_addrs()?
|
.to_socket_addrs()?
|
||||||
|
|
|
@ -10,3 +10,4 @@ console_log = "1"
|
||||||
console_error_panic_hook = "0.1"
|
console_error_panic_hook = "0.1"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
serde = { version = "1", features = ["derive"] }
|
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;
|
const ret = wasm.memory;
|
||||||
return addHeapObject(ret);
|
return addHeapObject(ret);
|
||||||
};
|
};
|
||||||
imports.wbg.__wbindgen_closure_wrapper4878 = function(arg0, arg1, arg2) {
|
imports.wbg.__wbindgen_closure_wrapper5350 = function(arg0, arg1, arg2) {
|
||||||
const ret = makeMutClosure(arg0, arg1, 530, __wbg_adapter_28);
|
const ret = makeMutClosure(arg0, arg1, 595, __wbg_adapter_28);
|
||||||
return addHeapObject(ret);
|
return addHeapObject(ret);
|
||||||
};
|
};
|
||||||
imports.wbg.__wbindgen_closure_wrapper4880 = function(arg0, arg1, arg2) {
|
imports.wbg.__wbindgen_closure_wrapper5352 = function(arg0, arg1, arg2) {
|
||||||
const ret = makeMutClosure(arg0, arg1, 528, __wbg_adapter_31);
|
const ret = makeMutClosure(arg0, arg1, 593, __wbg_adapter_31);
|
||||||
return addHeapObject(ret);
|
return addHeapObject(ret);
|
||||||
};
|
};
|
||||||
imports.wbg.__wbindgen_closure_wrapper10314 = function(arg0, arg1, arg2) {
|
imports.wbg.__wbindgen_closure_wrapper10787 = function(arg0, arg1, arg2) {
|
||||||
const ret = makeMutClosure(arg0, arg1, 634, __wbg_adapter_34);
|
const ret = makeMutClosure(arg0, arg1, 699, __wbg_adapter_34);
|
||||||
return addHeapObject(ret);
|
return addHeapObject(ret);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -744,7 +744,7 @@ async function __wbg_init(input) {
|
||||||
if (wasm !== undefined) return wasm;
|
if (wasm !== undefined) return wasm;
|
||||||
|
|
||||||
if (typeof input === 'undefined') {
|
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();
|
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>
|
<!DOCTYPE html><html><head>
|
||||||
|
|
||||||
<script type="module">
|
<script type="module">
|
||||||
import init, * as bindings from '/frontend-0e306a0cbb659ffacc2bde4bf6be791bdf3c4fd12ec49427513525c99e29cee2c56ae2ebb40ced499f14e919ae9c9bc1.js';
|
import init, * as bindings from '/frontend-570f3815d7fd92194c0bd8caac6e873c37520262d948b899db110969bdd20a82965a58d200fb559dee4285a369e3013a.js';
|
||||||
init('/frontend-0e306a0cbb659ffacc2bde4bf6be791bdf3c4fd12ec49427513525c99e29cee2c56ae2ebb40ced499f14e919ae9c9bc1_bg.wasm');
|
init('/frontend-570f3815d7fd92194c0bd8caac6e873c37520262d948b899db110969bdd20a82965a58d200fb559dee4285a369e3013a_bg.wasm');
|
||||||
window.wasmBindings = bindings;
|
window.wasmBindings = bindings;
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
@ -18,8 +18,8 @@ window.wasmBindings = bindings;
|
||||||
background-color: lightpink;
|
background-color: lightpink;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<link rel="preload" href="/frontend-0e306a0cbb659ffacc2bde4bf6be791bdf3c4fd12ec49427513525c99e29cee2c56ae2ebb40ced499f14e919ae9c9bc1_bg.wasm" as="fetch" type="application/wasm" crossorigin="anonymous" integrity="sha384-DjBqDLtln_rMK95L9r55G988T9EuxJQnUTUlyZ4pzuLFauLrtAztSZ8U6RmunJvB">
|
<link rel="preload" href="/frontend-570f3815d7fd92194c0bd8caac6e873c37520262d948b899db110969bdd20a82965a58d200fb559dee4285a369e3013a_bg.wasm" as="fetch" type="application/wasm" crossorigin="anonymous" integrity="sha384-Vw84Fdf9khlMC9jKrG6HPDdSAmLZSLiZ2xEJab3SCoKWWljSAPtVne5ChaNp4wE6">
|
||||||
<link rel="modulepreload" href="/frontend-0e306a0cbb659ffacc2bde4bf6be791bdf3c4fd12ec49427513525c99e29cee2c56ae2ebb40ced499f14e919ae9c9bc1.js" crossorigin="anonymous" integrity="sha384-HQ2tmiGi8E_nvn6D0oxqS6V_CrDQoH8CwgvZVzZ4Q55AQ5zpe83OGEg-McwNCVnE"></head>
|
<link rel="modulepreload" href="/frontend-570f3815d7fd92194c0bd8caac6e873c37520262d948b899db110969bdd20a82965a58d200fb559dee4285a369e3013a.js" crossorigin="anonymous" integrity="sha384-uvU8cJ-rIWGrY5c2Ms2A0n6dIWdhqLgCPj6u5m_zninPENzyXtefBjknYTthlevK"></head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<script>"use strict";
|
<script>"use strict";
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
use leptos::error::Result;
|
use leptos::error::Result;
|
||||||
use leptos::*;
|
use leptos::*;
|
||||||
|
use log::info;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
pub fn main() {
|
pub fn main() {
|
||||||
_ = console_log::init_with_level(log::Level::Debug);
|
_ = 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 {
|
pub fn fetch_example() -> impl IntoView {
|
||||||
let (cat_count, set_cat_count) = create_signal::<CatCount>(0);
|
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
|
// 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)
|
// (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 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 fallback = move |errors: RwSignal<Errors>| {
|
||||||
let error_list = move || {
|
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! {
|
view! {
|
||||||
|
{instance_view}
|
||||||
<div>
|
<div>
|
||||||
<label>
|
<label>
|
||||||
"How many cats would you like?"
|
"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!();
|
unimplemented!();
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue