This commit is contained in:
2025-02-04 23:32:53 +01:00
parent 73abd96f10
commit 503eeef20e
11 changed files with 250 additions and 233 deletions

64
Cargo.lock generated
View File

@@ -134,7 +134,7 @@ dependencies = [
"leptos_meta", "leptos_meta",
"leptos_router", "leptos_router",
"moonboard-parser", "moonboard-parser",
"rand", "rand 0.9.0",
"redb", "redb",
"ron", "ron",
"serde", "serde",
@@ -1482,7 +1482,7 @@ dependencies = [
"oco_ref", "oco_ref",
"or_poisoned", "or_poisoned",
"paste", "paste",
"rand", "rand 0.8.5",
"reactive_graph", "reactive_graph",
"rustc-hash", "rustc-hash",
"send_wrapper", "send_wrapper",
@@ -1998,7 +1998,7 @@ version = "0.2.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
dependencies = [ dependencies = [
"zerocopy", "zerocopy 0.7.35",
] ]
[[package]] [[package]]
@@ -2104,8 +2104,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [ dependencies = [
"libc", "libc",
"rand_chacha", "rand_chacha 0.3.1",
"rand_core", "rand_core 0.6.4",
]
[[package]]
name = "rand"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94"
dependencies = [
"rand_chacha 0.9.0",
"rand_core 0.9.0",
"zerocopy 0.8.16",
] ]
[[package]] [[package]]
@@ -2115,7 +2126,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [ dependencies = [
"ppv-lite86", "ppv-lite86",
"rand_core", "rand_core 0.6.4",
]
[[package]]
name = "rand_chacha"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
dependencies = [
"ppv-lite86",
"rand_core 0.9.0",
] ]
[[package]] [[package]]
@@ -2127,6 +2148,15 @@ dependencies = [
"getrandom", "getrandom",
] ]
[[package]]
name = "rand_core"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b08f3c9802962f7e1b25113931d94f43ed9725bebc59db9d0c3e9a23b67e15ff"
dependencies = [
"zerocopy 0.8.16",
]
[[package]] [[package]]
name = "reactive_graph" name = "reactive_graph"
version = "0.1.5" version = "0.1.5"
@@ -3326,7 +3356,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
dependencies = [ dependencies = [
"byteorder", "byteorder",
"zerocopy-derive", "zerocopy-derive 0.7.35",
]
[[package]]
name = "zerocopy"
version = "0.8.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b8c07a70861ce02bad1607b5753ecb2501f67847b9f9ada7c160fff0ec6300c"
dependencies = [
"zerocopy-derive 0.8.16",
] ]
[[package]] [[package]]
@@ -3340,6 +3379,17 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "zerocopy-derive"
version = "0.8.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5226bc9a9a9836e7428936cde76bb6b22feea1a8bfdbc0d241136e4d13417e25"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "zerofrom" name = "zerofrom"
version = "0.1.5" version = "0.1.5"

View File

@@ -27,7 +27,7 @@ leptos_axum = { version = "0.7", optional = true }
leptos_meta = { version = "0.7" } leptos_meta = { version = "0.7" }
leptos_router = { version = "0.7.0" } leptos_router = { version = "0.7.0" }
moonboard-parser = { workspace = true, optional = true } moonboard-parser = { workspace = true, optional = true }
rand = { version = "0.8", optional = true } rand = { version = "0.9", default-features = false, features = ["std_rng"] }
ron = { version = "0.8" } ron = { version = "0.8" }
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
server_fn = { version = "0.7.4", features = ["cbor"] } server_fn = { version = "0.7.4", features = ["cbor"] }
@@ -58,7 +58,6 @@ ssr = [
"dep:redb", "dep:redb",
"dep:bincode", "dep:bincode",
"dep:tokio", "dep:tokio",
"dep:rand",
"dep:tower", "dep:tower",
"dep:tower-http", "dep:tower-http",
"dep:leptos_axum", "dep:leptos_axum",

View File

@@ -1,3 +1,4 @@
use crate::codec::ron::Ron;
use crate::codec::ron::RonEncoded; use crate::codec::ron::RonEncoded;
use crate::models; use crate::models;
use crate::pages; use crate::pages;
@@ -5,7 +6,6 @@ use leptos::prelude::*;
use leptos_router::components::*; use leptos_router::components::*;
use leptos_router::path; use leptos_router::path;
use std::sync::Arc; use std::sync::Arc;
use type_toppings::ResultExt;
pub fn shell(options: LeptosOptions) -> impl IntoView { pub fn shell(options: LeptosOptions) -> impl IntoView {
use leptos_meta::MetaTags; use leptos_meta::MetaTags;
@@ -45,9 +45,9 @@ pub fn App() -> impl leptos::IntoView {
<Router> <Router>
<Routes fallback=|| "Not found"> <Routes fallback=|| "Not found">
<Route path=path!("/") view=Home /> <Route path=path!("/") view=Home />
<Route path=path!("/wall/:id") view=pages::wall::Wall /> <Route path=path!("/wall/:wall_uid") view=pages::wall::Wall />
<Route path=path!("/wall/:id/edit") view=pages::edit_wall::EditWall /> <Route path=path!("/wall/:wall_uid/edit") view=pages::edit_wall::EditWall />
<Route path=path!("/wall/:id/routes") view=pages::routes::Routes /> <Route path=path!("/wall/:wall_uid/routes") view=pages::routes::Routes />
</Routes> </Routes>
</Router> </Router>
} }
@@ -86,7 +86,11 @@ pub fn Home() -> impl leptos::IntoView {
leptos::view! {} leptos::view! {}
} }
#[server] #[server(
input = Ron,
output = Ron,
custom = RonEncoded
)]
#[tracing::instrument(skip_all, err)] #[tracing::instrument(skip_all, err)]
async fn get_walls() -> Result<RonEncoded<Vec<models::Wall>>, ServerFnError> { async fn get_walls() -> Result<RonEncoded<Vec<models::Wall>>, ServerFnError> {
use redb::ReadableTable; use redb::ReadableTable;
@@ -105,8 +109,6 @@ async fn get_walls() -> Result<RonEncoded<Vec<models::Wall>>, ServerFnError> {
tracing::debug!("opened table"); tracing::debug!("opened table");
let walls: Vec<models::Wall> = walls_table.iter()?.map(|r| r.map(|(_, v)| v.value())).collect::<Result<_, _>>()?; let walls: Vec<models::Wall> = walls_table.iter()?.map(|r| r.map(|(_, v)| v.value())).collect::<Result<_, _>>()?;
tracing::debug!("got walls {walls:?}");
Ok(walls) Ok(walls)
}) })
.await??; .await??;

View File

@@ -69,35 +69,6 @@ pub mod ron {
} }
} }
// impl<T> serde::Serialize for RonEncoded<T>
// where
// T: serde::Serialize,
// {
// #[tracing::instrument(skip_all, err)]
// fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
// where
// S: serde::Serializer,
// {
// let serialized = ron::to_string(&self.0).map_err(serde::ser::Error::custom)?;
// serializer.serialize_str(&serialized)
// }
// }
// impl<'de, T> serde::Deserialize<'de> for RonEncoded<T>
// where
// T: serde::de::DeserializeOwned + 'static,
// {
// #[tracing::instrument(skip_all, err)]
// fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
// where
// D: serde::Deserializer<'de>,
// {
// let s = String::deserialize(deserializer)?;
// let t: T = ron::from_str(&s).map_err(serde::de::Error::custom)?;
// Ok(Self(t))
// }
// }
// IntoReq // IntoReq
impl<T, Request, Err> IntoReq<Ron, Request, Err> for RonEncoded<T> impl<T, Request, Err> IntoReq<Ron, Request, Err> for RonEncoded<T>
where where
@@ -151,7 +122,6 @@ pub mod ron {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::Ron; use super::Ron;
use super::RonEncoded;
use codee::Decoder; use codee::Decoder;
use codee::Encoder; use codee::Encoder;
use serde::Deserialize; use serde::Deserialize;
@@ -163,27 +133,6 @@ pub mod ron {
value: i32, value: i32,
} }
// #[test]
// fn test_ron_wrapper() {
// let original = TestStruct {
// name: "Test".to_string(),
// value: 42,
// };
// // Wrap in RonCodec
// let wrapped = RonEncoded::new(original.clone());
// // Serialize
// let serialized = serde_json::to_string(&wrapped).expect("Serialization failed");
// println!("Serialized: {}", serialized);
// // Deserialize
// let deserialized: RonEncoded<TestStruct> = serde_json::from_str(&serialized).expect("Deserialization failed");
// // Compare
// assert_eq!(deserialized.into_inner(), original);
// }
#[test] #[test]
fn test_ron_codec() { fn test_ron_codec() {
let original = TestStruct { let original = TestStruct {

View File

@@ -4,6 +4,12 @@ use web_sys::MouseEvent;
#[component] #[component]
pub fn Button(#[prop(into)] text: String, onclick: impl Fn(MouseEvent) -> () + 'static) -> impl IntoView { pub fn Button(#[prop(into)] text: String, onclick: impl Fn(MouseEvent) -> () + 'static) -> impl IntoView {
view! { view! {
<button on:click=onclick type="button" class="text-black bg-orange-300 hover:bg-orange-400 focus:ring-4 focus:ring-orange-500 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 focus:outline-none">{ text }</button> <button
on:click=onclick
type="button"
class="text-black bg-orange-300 hover:bg-orange-400 focus:ring-4 focus:ring-orange-500 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 focus:outline-none"
>
{text}
</button>
} }
} }

View File

@@ -17,8 +17,8 @@ pub fn StyledHeader(items: HeaderItems) -> impl IntoView {
view! { view! {
<div class="bg-orange-300 text-black border-b-2 border-b-orange-400"> <div class="bg-orange-300 text-black border-b-2 border-b-orange-400">
// <div class="container mx-auto" > // <div class="container mx-auto" >
<Header items /> <Header items />
// </div> // </div>
</div> </div>
} }
} }

View File

@@ -11,6 +11,8 @@ pub mod components {
pub mod codec; pub mod codec;
pub mod server_functions;
#[cfg(feature = "ssr")] #[cfg(feature = "ssr")]
pub mod server; pub mod server;

View File

@@ -1,3 +1,4 @@
use crate::codec::ron::Ron;
use crate::codec::ron::RonEncoded; use crate::codec::ron::RonEncoded;
use crate::components::header::HeaderItem; use crate::components::header::HeaderItem;
use crate::components::header::HeaderItems; use crate::components::header::HeaderItems;
@@ -35,17 +36,17 @@ pub fn EditWall() -> impl leptos::IntoView {
right: vec![], right: vec![],
}; };
leptos::view! { // leptos::view! {
<div class="min-w-screen min-h-screen bg-slate-900"> // <div class="min-w-screen min-h-screen bg-slate-900">
<StyledHeader items=header_items /> // <StyledHeader items=header_items />
<div class="container mx-auto mt-2"> // <div class="container mx-auto mt-2">
<Await future=load let:data> // <Await future=load let:data>
<Ready data=data.deref().to_owned() /> // <Ready data=data.deref().to_owned() />
</Await> // </Await>
</div> // </div>
</div> // </div>
} // }
} }
#[component] #[component]
@@ -149,7 +150,12 @@ pub struct Image {
file_contents: Vec<u8>, file_contents: Vec<u8>,
} }
#[server] #[server(
input = Ron,
output = Ron,
custom = RonEncoded
)]
#[tracing::instrument(skip_all, err)]
async fn load_initial_data() -> Result<RonEncoded<InitialData>, ServerFnError> { async fn load_initial_data() -> Result<RonEncoded<InitialData>, ServerFnError> {
todo!() todo!()
// let wall = state.persistent.with(|s| s.wall.clone()).await; // let wall = state.persistent.with(|s| s.wall.clone()).await;
@@ -157,7 +163,7 @@ async fn load_initial_data() -> Result<RonEncoded<InitialData>, ServerFnError> {
} }
#[server(name = SetImage, input = Cbor)] #[server(name = SetImage, input = Cbor)]
#[tracing::instrument(skip(image))] #[tracing::instrument(skip(image), err)]
async fn set_image(hold_position: HoldPosition, image: Image) -> Result<models::Hold, ServerFnError> { async fn set_image(hold_position: HoldPosition, image: Image) -> Result<models::Hold, ServerFnError> {
tracing::info!("Setting image, {}, {} bytes", image.file_name, image.file_contents.len()); tracing::info!("Setting image, {}, {} bytes", image.file_name, image.file_contents.len());

View File

@@ -1,20 +1,42 @@
use crate::codec::ron::Ron;
use crate::codec::ron::RonEncoded; use crate::codec::ron::RonEncoded;
use crate::components::header::HeaderItem; use crate::components::header::HeaderItem;
use crate::components::header::HeaderItems; use crate::components::header::HeaderItems;
use crate::components::header::StyledHeader; use crate::components::header::StyledHeader;
use crate::models; use crate::models;
use crate::models::WallUid;
use leptos::Params;
use leptos::prelude::*; use leptos::prelude::*;
use leptos_router::params::Params;
use serde::Deserialize; use serde::Deserialize;
use serde::Serialize; use serde::Serialize;
use std::collections::BTreeSet; use std::collections::BTreeSet;
use std::ops::Deref; use std::ops::Deref;
#[derive(Params, PartialEq, Clone)]
struct RouteParams {
wall_uid: Option<models::WallUid>,
}
#[component] #[component]
#[tracing::instrument(skip_all)]
pub fn Routes() -> impl leptos::IntoView { pub fn Routes() -> impl leptos::IntoView {
let load = async move { tracing::debug!("Enter");
// TODO: What to do about this unwrap?
load_initial_data().await.unwrap() let params = leptos_router::hooks::use_params::<RouteParams>();
};
let problems = Resource::<Option<models::Wall>, Ron>::new_with_options(
move || params.get().map(|p| p.wall_uid),
move |wall_uid: Result<Option<WallUid>, _>| async move {
if let Ok(Some(wall_uid)) = wall_uid {
let wall = crate::server_functions::get_wall(wall_uid).await.unwrap().into_inner();
Some(wall)
} else {
None
}
},
false,
);
let header_items = HeaderItems { let header_items = HeaderItems {
left: vec![HeaderItem { left: vec![HeaderItem {
@@ -57,27 +79,6 @@ fn Ready(data: InitialData) -> impl leptos::IntoView {
} }
} }
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct InitialData {
problems: BTreeSet<models::Problem>,
}
#[server]
async fn load_initial_data() -> Result<RonEncoded<InitialData>, ServerFnError> {
todo!()
// let state = expect_context::<State>();
// let problems = state
// .persistent
// .with(|s| {
// let problems = &s.problems.problems;
// problems.clone()
// })
// .await;
// Ok(RonCodec::new(InitialData { problems }))
}
#[server(name = ImportFromMiniMoonboard)] #[server(name = ImportFromMiniMoonboard)]
#[tracing::instrument] #[tracing::instrument]
async fn import_from_mini_moonboard() -> Result<(), ServerFnError> { async fn import_from_mini_moonboard() -> Result<(), ServerFnError> {

View File

@@ -1,5 +1,4 @@
use crate::codec::ron::Ron; use crate::codec::ron::Ron;
use crate::codec::ron::RonEncoded;
use crate::components::button::Button; use crate::components::button::Button;
use crate::components::header::HeaderItem; use crate::components::header::HeaderItem;
use crate::components::header::HeaderItems; use crate::components::header::HeaderItems;
@@ -11,45 +10,33 @@ use leptos::Params;
use leptos::prelude::*; use leptos::prelude::*;
use leptos::reactive::graph::ReactiveNode; use leptos::reactive::graph::ReactiveNode;
use leptos_router::params::Params; use leptos_router::params::Params;
use std::sync::Arc; use rand::SeedableRng;
#[derive(Params, PartialEq, Clone)] #[derive(Params, PartialEq, Clone)]
struct WallParams { struct RouteParams {
id: Option<models::WallUid>, wall_uid: Option<models::WallUid>,
} }
#[component] #[component]
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
pub fn Wall() -> impl leptos::IntoView { pub fn Wall() -> impl leptos::IntoView {
tracing::debug!("Enter"); tracing::debug!("Enter");
let params = leptos_router::hooks::use_params::<WallParams>();
// TODO let params = leptos_router::hooks::use_params::<RouteParams>();
let wall = Resource::<models::Wall, Ron>::new_with_options(
move || params.get().unwrap().id, let wall = Resource::<Option<models::Wall>, Ron>::new_with_options(
{ move || params.get().map(|p| p.wall_uid),
move |wall_uid: Option<WallUid>| async move { move |wall_uid: Result<Option<WallUid>, _>| async move {
let wall_uid = wall_uid.unwrap(); if let Ok(Some(wall_uid)) = wall_uid {
let wall = get_wall(wall_uid).await.unwrap().into_inner(); let wall = crate::server_functions::get_wall(wall_uid).await.unwrap().into_inner();
wall Some(wall)
} else {
None
} }
}, },
false, false,
); );
// // TODO
// let wall = Resource::new(
// move || params.get().unwrap().id,
// move |wall_id| async move {
// if let Some(wall_id) = wall_id {
// let wall = get_wall(wall_id).await.unwrap();
// Some(wall)
// } else {
// None
// }
// },
// );
let header_items = HeaderItems { let header_items = HeaderItems {
left: vec![], left: vec![],
middle: vec![HeaderItem { middle: vec![HeaderItem {
@@ -69,23 +56,23 @@ pub fn Wall() -> impl leptos::IntoView {
}; };
leptos::view! { leptos::view! {
<div class="min-w-screen min-h-screen bg-slate-900"> <div class="min-w-screen min-h-screen bg-slate-900">
<StyledHeader items=header_items /> <StyledHeader items=header_items />
<div class="m-2"> <div class="m-2">
<Suspense fallback=move || view! {<p>"Loading..."</p>}> <Suspense fallback=move || {
{move || Suspend::new(async move{ view! { <p>"Loading..."</p> }
let wall: Option<Option<RonEncoded<models::Wall>>> = wall.get(); }>
wall.map(|wall|{let wall = wall.unwrap(); {move || Suspend::new(async move {
view! { let wall: Option<models::Wall> = wall.get().flatten();
<Ready wall=wall.into_inner() /> wall.map(|wall| {
} view! { <Ready wall/> }
}) })
})} })}
</Suspense> </Suspense>
</div>
</div> </div>
} </div>
}
} }
#[component] #[component]
@@ -93,14 +80,33 @@ fn Ready(wall: models::Wall) -> impl leptos::IntoView {
tracing::debug!("ready"); tracing::debug!("ready");
let (current_problem, current_problem_writer) = signal(None::<models::Problem>); let (current_problem, current_problem_writer) = signal(None::<models::Problem>);
let problem_fetcher = LocalResource::new(move || async move { let problem_fetcher = {
tracing::info!("Loading random problem"); LocalResource::new(move || {
let problem = get_random_problem().await.expect("cannot get random problem"); let wall_uid = wall.uid;
if problem.is_none() { let problems = wall.problems.clone();
tracing::info!("No problem returned by server in response to request for random problem");
} async move {
current_problem_writer.set(problem.into_inner()); tracing::info!("Loading random problem");
});
// TODO: seed properly
use rand::seq::IteratorRandom;
let rng = &mut rand::rngs::StdRng::seed_from_u64(0);
let random_problem = problems.iter().choose(rng);
let problem = if let Some(random_problem) = random_problem {
crate::server_functions::get_problem(wall_uid, *random_problem)
.await
.expect("cannot get random problem")
.into_inner()
} else {
tracing::info!("Wall has no problems");
None
};
current_problem_writer.set(problem);
}
})
};
let mut cells = vec![]; let mut cells = vec![];
for (&hold_position, hold) in &wall.holds { for (&hold_position, hold) in &wall.holds {
@@ -117,17 +123,18 @@ fn Ready(wall: models::Wall) -> impl leptos::IntoView {
view! { view! {
<div class="grid grid-cols-[auto,1fr] gap-8"> <div class="grid grid-cols-[auto,1fr] gap-8">
// Render the wall // Render the wall
<div style="max-height: 90vh; max-width: 90vh;" class=move || { grid_classes.clone() }>{cells}</div> <div style="max-height: 90vh; max-width: 90vh;" class=move || { grid_classes.clone() }>
{cells}
</div>
<div> <div>
<div> <div>// TODO:
// TODO: // <p>{current_problem.read().as_ref().map(|p| p.name.clone())}</p>
// <p>{current_problem.read().as_ref().map(|p| p.name.clone())}</p> // <p>{current_problem.read().as_ref().map(|p| p.set_by.clone())}</p>
// <p>{current_problem.read().as_ref().map(|p| p.set_by.clone())}</p>
</div> </div>
<Button onclick=move |_| problem_fetcher.mark_dirty() text="➤ Next problem" /> <Button onclick=move |_| problem_fetcher.mark_dirty() text="➤ Next problem" />
</ div> </div>
</div> </div>
} }
} }
@@ -161,74 +168,3 @@ fn Hold(hold: models::Hold, #[prop(into)] role: Signal<Option<HoldRole>>) -> imp
tracing::trace!("view"); tracing::trace!("view");
view! { <div class=class>{img}</div> } view! { <div class=class>{img}</div> }
} }
#[server(
input = Ron,
output = Ron,
custom = RonEncoded
)]
#[tracing::instrument(skip_all, err)]
async fn get_wall(wall_uid: models::WallUid) -> Result<RonEncoded<models::Wall>, ServerFnError> {
tracing::debug!("Enter");
#[derive(Debug, derive_more::Error, derive_more::Display)]
enum Error {
#[display("Wall not found: {_0:?}")]
NotFound(#[error(not(source))] models::WallUid),
}
let db = expect_context::<Arc<redb::Database>>();
let wall = tokio::task::spawn_blocking(move || -> Result<models::Wall, ServerFnError> {
let read_txn = db.begin_read()?;
let walls_table = read_txn.open_table(crate::server::db::current::TABLE_WALLS)?;
let wall = walls_table.get(wall_uid)?.ok_or(Error::NotFound(wall_uid))?.value();
Ok(wall)
})
.await??;
tracing::debug!("ok");
Ok(RonEncoded::new(wall))
}
#[server(
input = Ron,
output = Ron,
custom = RonEncoded
)]
#[tracing::instrument(skip_all, err)]
async fn get_problem(wall_uid: models::WallUid, problem_uid: models::ProblemUid) -> Result<RonEncoded<Option<models::Problem>>, ServerFnError> {
tracing::debug!("Enter");
let db = expect_context::<Arc<redb::Database>>();
let problem = tokio::task::spawn_blocking(move || -> Result<Option<models::Problem>, ServerFnError> {
let read_txn = db.begin_read()?;
let table = read_txn.open_table(crate::server::db::current::TABLE_PROBLEMS)?;
let problem = table.get((wall_uid, problem_uid))?.map(|guard| guard.value());
Ok(problem)
})
.await??;
// use rand::seq::IteratorRandom;
// let state = expect_context::<State>();
// let problem = state
// .persistent
// .with(|s| {
// let problems = &s.problems.problems;
// let rng = &mut rand::thread_rng();
// problems.iter().choose(rng).cloned()
// })
// .await;
// tracing::debug!("Returning randomized problem: {problem:?}");
Ok(RonEncoded::new(problem))
}

View File

@@ -0,0 +1,66 @@
use crate::codec::ron::Ron;
use crate::codec::ron::RonEncoded;
use crate::models;
use leptos::prelude::expect_context;
use leptos::server;
use server_fn::ServerFnError;
use std::sync::Arc;
#[server(
input = Ron,
output = Ron,
custom = RonEncoded
)]
#[tracing::instrument(skip_all, err)]
pub(crate) async fn get_wall(wall_uid: models::WallUid) -> Result<RonEncoded<models::Wall>, ServerFnError> {
tracing::debug!("Enter");
#[derive(Debug, derive_more::Error, derive_more::Display)]
enum Error {
#[display("Wall not found: {_0:?}")]
NotFound(#[error(not(source))] models::WallUid),
}
let db = expect_context::<Arc<redb::Database>>();
let wall = tokio::task::spawn_blocking(move || -> Result<models::Wall, ServerFnError> {
let read_txn = db.begin_read()?;
let walls_table = read_txn.open_table(crate::server::db::current::TABLE_WALLS)?;
let wall = walls_table.get(wall_uid)?.ok_or(Error::NotFound(wall_uid))?.value();
Ok(wall)
})
.await??;
tracing::debug!("ok");
Ok(RonEncoded::new(wall))
}
#[server(
input = Ron,
output = Ron,
custom = RonEncoded
)]
#[tracing::instrument(skip_all, err)]
pub(crate) async fn get_problem(
wall_uid: models::WallUid,
problem_uid: models::ProblemUid,
) -> Result<RonEncoded<Option<models::Problem>>, ServerFnError> {
tracing::debug!("Enter");
let db = expect_context::<Arc<redb::Database>>();
let problem = tokio::task::spawn_blocking(move || -> Result<Option<models::Problem>, ServerFnError> {
let read_txn = db.begin_read()?;
let table = read_txn.open_table(crate::server::db::current::TABLE_PROBLEMS)?;
let problem = table.get((wall_uid, problem_uid))?.map(|guard| guard.value());
Ok(problem)
})
.await??;
Ok(RonEncoded::new(problem))
}