wip
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -128,6 +128,7 @@ dependencies = [
|
||||
"confik",
|
||||
"console_error_panic_hook",
|
||||
"derive_more",
|
||||
"error_reporter",
|
||||
"http 1.2.0",
|
||||
"leptos",
|
||||
"leptos_axum",
|
||||
|
||||
@@ -38,7 +38,7 @@ tower-http = { version = "0.5", features = ["fs"], optional = true }
|
||||
tracing = { version = "0.1" }
|
||||
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
|
||||
tracing-subscriber-wasm = "0.1.0"
|
||||
type-toppings = { version = "0.2.1", features = ["result"] }
|
||||
type-toppings = { version = "0.2.1", features = ["result", "iterator"] }
|
||||
wasm-bindgen = "=0.2.99"
|
||||
web-sys = { version = "0.3.76", features = ["File", "FileList"] }
|
||||
xdg = { version = "2.5", optional = true }
|
||||
@@ -47,6 +47,7 @@ redb = { version = "2.4", optional = true }
|
||||
bincode = { version = "1.3", optional = true }
|
||||
serde_json = { version = "1" }
|
||||
codee = { version = "0.3" }
|
||||
error_reporter = { version = "1" }
|
||||
|
||||
[dev-dependencies.serde_json]
|
||||
version = "1"
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
use crate::codec::ron::Ron;
|
||||
use crate::codec::ron::RonEncoded;
|
||||
use crate::models;
|
||||
use crate::pages;
|
||||
use leptos::prelude::*;
|
||||
use leptos_router::components::*;
|
||||
use leptos_router::path;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub fn shell(options: LeptosOptions) -> impl IntoView {
|
||||
use leptos_meta::MetaTags;
|
||||
@@ -61,7 +57,7 @@ pub fn Home() -> impl leptos::IntoView {
|
||||
|
||||
let action = Action::new(|()| async move {
|
||||
tracing::debug!("running action");
|
||||
let walls = get_walls()
|
||||
let walls = crate::server_functions::get_walls()
|
||||
.await
|
||||
.inspect_err(|e| {
|
||||
dbg!(e);
|
||||
@@ -85,33 +81,3 @@ pub fn Home() -> impl leptos::IntoView {
|
||||
|
||||
leptos::view! {}
|
||||
}
|
||||
|
||||
#[server(
|
||||
input = Ron,
|
||||
output = Ron,
|
||||
custom = RonEncoded
|
||||
)]
|
||||
#[tracing::instrument(skip_all, err)]
|
||||
async fn get_walls() -> Result<RonEncoded<Vec<models::Wall>>, ServerFnError> {
|
||||
use redb::ReadableTable;
|
||||
|
||||
tracing::debug!("get walls");
|
||||
|
||||
let db = expect_context::<Arc<redb::Database>>();
|
||||
tracing::debug!("got db from context");
|
||||
|
||||
let walls = tokio::task::spawn_blocking(move || -> Result<Vec<models::Wall>, ServerFnError> {
|
||||
tracing::debug!("beginning read transaction");
|
||||
let read_txn = db.begin_read()?;
|
||||
|
||||
tracing::debug!("opening table");
|
||||
let walls_table = read_txn.open_table(crate::server::db::current::TABLE_WALLS)?;
|
||||
tracing::debug!("opened table");
|
||||
let walls: Vec<models::Wall> = walls_table.iter()?.map(|r| r.map(|(_, v)| v.value())).collect::<Result<_, _>>()?;
|
||||
|
||||
Ok(walls)
|
||||
})
|
||||
.await??;
|
||||
|
||||
Ok(RonEncoded::new(walls))
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ pub mod v2 {
|
||||
pub problems: BTreeSet<ProblemUid>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, derive_more::FromStr, derive_more::Display)]
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash, derive_more::FromStr, derive_more::Display)]
|
||||
pub struct WallUid(pub uuid::Uuid);
|
||||
impl WallUid {
|
||||
pub fn new() -> Self {
|
||||
@@ -50,7 +50,7 @@ pub mod v2 {
|
||||
pub date_added: chrono::DateTime<chrono::Utc>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
|
||||
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash, derive_more::FromStr, derive_more::Display)]
|
||||
pub struct ProblemUid(pub uuid::Uuid);
|
||||
impl ProblemUid {
|
||||
pub fn new() -> Self {
|
||||
|
||||
@@ -15,6 +15,7 @@ use std::ops::Deref;
|
||||
|
||||
#[derive(Params, PartialEq, Clone)]
|
||||
struct RouteParams {
|
||||
// Is never None
|
||||
wall_uid: Option<models::WallUid>,
|
||||
}
|
||||
|
||||
@@ -24,12 +25,13 @@ pub fn Routes() -> impl leptos::IntoView {
|
||||
tracing::debug!("Enter");
|
||||
|
||||
let params = leptos_router::hooks::use_params::<RouteParams>();
|
||||
let wall_uid = Signal::derive(move || params.get().map(|p| p.wall_uid.expect("wall_uid param is never None")));
|
||||
|
||||
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();
|
||||
let problems = Resource::<Option<Vec<models::Problem>>, Ron>::new_with_options(
|
||||
move || wall_uid.get(),
|
||||
move |wall_uid: Result<WallUid, _>| async move {
|
||||
if let Ok(wall_uid) = wall_uid {
|
||||
let wall = crate::server_functions::get_problems_for_wall(wall_uid).await.unwrap().into_inner();
|
||||
Some(wall)
|
||||
} else {
|
||||
None
|
||||
@@ -55,22 +57,43 @@ pub fn Routes() -> impl leptos::IntoView {
|
||||
<StyledHeader items=header_items />
|
||||
|
||||
<div class="container mx-auto mt-2">
|
||||
<Await future=load let:data>
|
||||
<Ready data=data.deref().to_owned() />
|
||||
</Await>
|
||||
{wall_uid.get().map(|wall_uid| view! {<Import wall_uid/>})}
|
||||
|
||||
<Suspense fallback=|| view! {"loading"}>
|
||||
<For
|
||||
each=move || problems.get().flatten().into_iter().flat_map(|v| v.into_iter())
|
||||
key=|problem| problem.uid
|
||||
children=move |problem: models::Problem| {
|
||||
view! {
|
||||
<Problem problem/>
|
||||
}
|
||||
}
|
||||
/>
|
||||
</Suspense>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn Ready(data: InitialData) -> impl leptos::IntoView {
|
||||
#[tracing::instrument(skip_all)]
|
||||
fn Problem(problem: models::Problem) -> impl IntoView {
|
||||
tracing::debug!("Enter");
|
||||
|
||||
view! {
|
||||
<p>"problem"</p>
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
#[tracing::instrument(skip_all)]
|
||||
fn Import(wall_uid: WallUid) -> impl IntoView {
|
||||
tracing::debug!("ready");
|
||||
|
||||
let import_from_mini_moonboard = Action::from(ServerAction::<ImportFromMiniMoonboard>::new());
|
||||
|
||||
let onclick = move |_mouse_event| {
|
||||
import_from_mini_moonboard.dispatch(ImportFromMiniMoonboard {});
|
||||
import_from_mini_moonboard.dispatch(ImportFromMiniMoonboard { wall_uid });
|
||||
};
|
||||
|
||||
view! {
|
||||
@@ -81,17 +104,17 @@ fn Ready(data: InitialData) -> impl leptos::IntoView {
|
||||
|
||||
#[server(name = ImportFromMiniMoonboard)]
|
||||
#[tracing::instrument]
|
||||
async fn import_from_mini_moonboard() -> Result<(), ServerFnError> {
|
||||
async fn import_from_mini_moonboard(wall_uid: WallUid) -> Result<(), ServerFnError> {
|
||||
use crate::server::config::Config;
|
||||
use crate::server::db::Database;
|
||||
|
||||
todo!()
|
||||
// tracing::info!("Importing mini moonboard problems");
|
||||
tracing::info!("Importing mini moonboard problems");
|
||||
|
||||
// let config = expect_context::<Config>();
|
||||
// let state = expect_context::<State>();
|
||||
let config = expect_context::<Config>();
|
||||
let db = expect_context::<Database>();
|
||||
|
||||
// crate::server::operations::import_mini_moonboard_problems(&config, &state).await?;
|
||||
crate::server::operations::import_mini_moonboard_problems(&config, db, wall_uid).await?;
|
||||
|
||||
// // TODO: Return information about what was done
|
||||
// Ok(())
|
||||
// TODO: Return information about what was done
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -23,11 +23,12 @@ pub fn Wall() -> impl leptos::IntoView {
|
||||
tracing::debug!("Enter");
|
||||
|
||||
let params = leptos_router::hooks::use_params::<RouteParams>();
|
||||
let wall_uid = move || params.get().map(|p| p.wall_uid.expect("wall_uid param is never None"));
|
||||
|
||||
let wall = 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 {
|
||||
move || wall_uid(),
|
||||
move |wall_uid: Result<WallUid, _>| async move {
|
||||
if let Ok(wall_uid) = wall_uid {
|
||||
let wall = crate::server_functions::get_wall(wall_uid).await.unwrap().into_inner();
|
||||
Some(wall)
|
||||
} else {
|
||||
@@ -37,7 +38,7 @@ pub fn Wall() -> impl leptos::IntoView {
|
||||
false,
|
||||
);
|
||||
|
||||
let header_items = HeaderItems {
|
||||
let header_items = move || HeaderItems {
|
||||
left: vec![],
|
||||
middle: vec![HeaderItem {
|
||||
text: "ASCEND".to_string(),
|
||||
@@ -46,18 +47,18 @@ pub fn Wall() -> impl leptos::IntoView {
|
||||
right: vec![
|
||||
HeaderItem {
|
||||
text: "Routes".to_string(),
|
||||
link: Some("/wall/routes".to_string()),
|
||||
link: wall_uid().map(|uid| format!("/wall/{uid}/routes")).ok(),
|
||||
},
|
||||
HeaderItem {
|
||||
text: "Holds".to_string(),
|
||||
link: Some("/wall/edit".to_string()),
|
||||
link: wall_uid().map(|uid| format!("/wall/{uid}/edit")).ok(),
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
leptos::view! {
|
||||
<div class="min-w-screen min-h-screen bg-slate-900">
|
||||
<StyledHeader items=header_items />
|
||||
<StyledHeader items=header_items() />
|
||||
|
||||
<div class="m-2">
|
||||
<Suspense fallback=move || {
|
||||
@@ -76,6 +77,7 @@ pub fn Wall() -> impl leptos::IntoView {
|
||||
}
|
||||
|
||||
#[component]
|
||||
#[tracing::instrument(skip_all)]
|
||||
fn Ready(wall: models::Wall) -> impl leptos::IntoView {
|
||||
tracing::debug!("ready");
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ use config::Config;
|
||||
use confik::Configuration;
|
||||
use confik::EnvSource;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use tower_http::services::ServeDir;
|
||||
use tracing::level_filters::LevelFilter;
|
||||
use type_toppings::ResultExt;
|
||||
@@ -51,7 +50,7 @@ async fn serve(cli: Cli) -> Result<(), Error> {
|
||||
use leptos_axum::generate_route_list;
|
||||
|
||||
tracing::debug!("Creating DB");
|
||||
let db = Arc::new(db::create()?);
|
||||
let db = db::Database::create()?;
|
||||
|
||||
migrations::run_migrations(&db).await.map_err(Error::Migration)?;
|
||||
|
||||
@@ -73,7 +72,7 @@ async fn serve(cli: Cli) -> Result<(), Error> {
|
||||
&leptos_options,
|
||||
routes,
|
||||
move || {
|
||||
leptos::prelude::provide_context::<Arc<redb::Database>>(Arc::clone(&db));
|
||||
leptos::prelude::provide_context::<db::Database>(db.clone());
|
||||
leptos::prelude::provide_context::<Config>(config.clone())
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,32 +1,74 @@
|
||||
use bincode::Bincode;
|
||||
use redb::Database;
|
||||
use redb::ReadTransaction;
|
||||
use redb::TableDefinition;
|
||||
use redb::WriteTransaction;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
mod bincode;
|
||||
|
||||
pub const DB_FILE: &str = "datastore/private/ascend.redb";
|
||||
const DB_FILE: &str = "datastore/private/ascend.redb";
|
||||
|
||||
#[tracing::instrument(skip_all, err)]
|
||||
pub fn create() -> Result<Database, redb::Error> {
|
||||
let file = PathBuf::from(DB_FILE);
|
||||
|
||||
// Create parent dirs
|
||||
if let Some(parent_dir) = file.parent() {
|
||||
std::fs::create_dir_all(parent_dir)?;
|
||||
}
|
||||
|
||||
let db = Database::create(file)?;
|
||||
Ok(db)
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Database {
|
||||
db: Arc<redb::Database>,
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub fn get_version(db: &Database) -> Result<Option<Version>, redb::Error> {
|
||||
let txn = db.begin_read()?;
|
||||
let version = txn.open_table(TABLE_VERSION)?.get(())?.map(|v| v.value());
|
||||
Ok(version)
|
||||
impl Database {
|
||||
#[tracing::instrument(skip_all, err)]
|
||||
pub fn create() -> Result<Database, redb::Error> {
|
||||
let file = PathBuf::from(DB_FILE);
|
||||
|
||||
// Create parent dirs
|
||||
if let Some(parent_dir) = file.parent() {
|
||||
std::fs::create_dir_all(parent_dir)?;
|
||||
}
|
||||
|
||||
let db = redb::Database::create(file)?;
|
||||
Ok(Self { db: Arc::new(db) })
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all, err)]
|
||||
pub async fn read<T>(&self, f: impl FnOnce(&'_ ReadTransaction) -> Result<T, DatabaseOperationError>) -> Result<T, DatabaseOperationError> {
|
||||
tokio::task::block_in_place(|| {
|
||||
let dbtx = self.db.begin_read()?;
|
||||
f(&dbtx)
|
||||
})
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all, err)]
|
||||
pub async fn write<T>(&self, f: impl FnOnce(&'_ WriteTransaction) -> Result<T, DatabaseOperationError>) -> Result<T, DatabaseOperationError> {
|
||||
tokio::task::block_in_place(|| {
|
||||
let dbtx = self.db.begin_write()?;
|
||||
let res = f(&dbtx)?;
|
||||
dbtx.commit()?;
|
||||
Ok(res)
|
||||
})
|
||||
}
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub async fn get_version(&self) -> Result<Option<Version>, DatabaseOperationError> {
|
||||
self.read(|dbtx| dbtx.open_table(TABLE_VERSION)?.get(()).map(|o| o.map(|v| v.value())).map_err(Into::into))
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, derive_more::Error, derive_more::Display, derive_more::From)]
|
||||
#[display("DB operation error: {_variant}")]
|
||||
pub enum DatabaseOperationError {
|
||||
#[display("redb error")]
|
||||
#[from(forward)]
|
||||
Redb(#[error(source)] redb::Error),
|
||||
|
||||
#[from(ignore)]
|
||||
Custom(#[error(source)] Box<dyn std::error::Error + Send + Sync + 'static>),
|
||||
}
|
||||
impl DatabaseOperationError {
|
||||
pub fn custom(err: impl std::error::Error + Send + Sync + 'static) -> Self {
|
||||
Self::Custom(Box::new(err))
|
||||
}
|
||||
}
|
||||
|
||||
pub const TABLE_VERSION: TableDefinition<(), Bincode<Version>> = TableDefinition::new("version");
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::db;
|
||||
use super::db::Database;
|
||||
use crate::models;
|
||||
use redb::Database;
|
||||
use redb::ReadableTable;
|
||||
use redb::ReadableTableMetadata;
|
||||
use std::collections::BTreeSet;
|
||||
@@ -31,13 +31,12 @@ async fn migrate_from_ron_to_redb(db: &Database) -> Result<(), Box<dyn std::erro
|
||||
ron::from_str(&content)?
|
||||
};
|
||||
|
||||
let write_txn = db.begin_write()?;
|
||||
{
|
||||
let mut version_table = write_txn.open_table(db::TABLE_VERSION)?;
|
||||
db.write(|txn| {
|
||||
let mut version_table = txn.open_table(db::TABLE_VERSION)?;
|
||||
assert!(version_table.is_empty()?);
|
||||
version_table.insert((), db::Version { version: 1 })?;
|
||||
|
||||
let mut root_table = write_txn.open_table(db::v1::TABLE_ROOT)?;
|
||||
let mut root_table = txn.open_table(db::v1::TABLE_ROOT)?;
|
||||
assert!(root_table.is_empty()?);
|
||||
|
||||
let persistent_state = models::v1::PersistentState {
|
||||
@@ -47,8 +46,9 @@ async fn migrate_from_ron_to_redb(db: &Database) -> Result<(), Box<dyn std::erro
|
||||
};
|
||||
|
||||
root_table.insert((), persistent_state)?;
|
||||
}
|
||||
write_txn.commit()?;
|
||||
Ok(())
|
||||
})
|
||||
.await?;
|
||||
|
||||
tracing::info!("Removing ron state");
|
||||
tokio::fs::remove_file(ron_state_file_path).await?;
|
||||
@@ -60,8 +60,7 @@ async fn migrate_from_ron_to_redb(db: &Database) -> Result<(), Box<dyn std::erro
|
||||
// TODO: Move out, is not really a migration
|
||||
#[tracing::instrument(skip_all, err)]
|
||||
async fn init_at_current_version(db: &Database) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let txn = db.begin_write()?;
|
||||
{
|
||||
db.write(|txn| {
|
||||
let mut version_table = txn.open_table(db::TABLE_VERSION)?;
|
||||
let is_missing_version = version_table.get(())?.is_none();
|
||||
if is_missing_version {
|
||||
@@ -90,8 +89,10 @@ async fn init_at_current_version(db: &Database) -> Result<(), Box<dyn std::error
|
||||
assert!(table.is_empty()?);
|
||||
}
|
||||
}
|
||||
}
|
||||
txn.commit()?;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -100,8 +101,7 @@ async fn init_at_current_version(db: &Database) -> Result<(), Box<dyn std::error
|
||||
async fn migrate_to_v2(db: &Database) -> Result<(), Box<dyn std::error::Error>> {
|
||||
use super::db;
|
||||
|
||||
let txn = db.begin_write()?;
|
||||
{
|
||||
db.write(|txn| {
|
||||
let mut version_table = txn.open_table(db::TABLE_VERSION)?;
|
||||
let version = version_table.get(())?.unwrap().value().version;
|
||||
if version == 1 {
|
||||
@@ -158,9 +158,14 @@ async fn migrate_to_v2(db: &Database) -> Result<(), Box<dyn std::error::Error>>
|
||||
let mut walls_table = txn.open_table(db::v2::TABLE_WALLS)?;
|
||||
walls_table.insert(wall_v2.uid, wall_v2)?;
|
||||
drop(walls_table);
|
||||
|
||||
let problems_table = txn.open_table(db::v2::TABLE_PROBLEMS)?;
|
||||
drop(problems_table);
|
||||
}
|
||||
}
|
||||
txn.commit()?;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
//! Server lib module to host re-usable server operations.
|
||||
|
||||
use super::db::Database;
|
||||
use crate::models;
|
||||
use crate::models::HoldPosition;
|
||||
use crate::models::HoldRole;
|
||||
use crate::server::config::Config;
|
||||
use crate::server::db;
|
||||
use redb::Database;
|
||||
use redb::ReadableTable;
|
||||
use std::collections::BTreeMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub(crate) async fn import_mini_moonboard_problems(config: &Config, db: Arc<Database>, wall_id: models::WallUid) -> Result<(), Error> {
|
||||
pub(crate) async fn import_mini_moonboard_problems(config: &Config, db: Database, wall_uid: models::WallUid) -> Result<(), Error> {
|
||||
use moonboard_parser::mini_moonboard;
|
||||
|
||||
let mut problems = Vec::new();
|
||||
@@ -61,26 +60,22 @@ pub(crate) async fn import_mini_moonboard_problems(config: &Config, db: Arc<Data
|
||||
problems.push(problem);
|
||||
}
|
||||
|
||||
tokio::task::spawn_blocking(move || -> Result<(), redb::Error> {
|
||||
let write_txn = db.begin_write()?;
|
||||
{
|
||||
let mut walls_table = write_txn.open_table(db::current::TABLE_WALLS)?;
|
||||
let mut problems_table = write_txn.open_table(db::current::TABLE_PROBLEMS)?;
|
||||
db.write(|txn| {
|
||||
let mut walls_table = txn.open_table(db::current::TABLE_WALLS)?;
|
||||
let mut problems_table = txn.open_table(db::current::TABLE_PROBLEMS)?;
|
||||
|
||||
let mut wall = walls_table.get(wall_id)?.unwrap().value();
|
||||
wall.problems.extend(problems.iter().map(|p| p.uid));
|
||||
walls_table.insert(wall_id, wall)?;
|
||||
let mut wall = walls_table.get(wall_uid)?.unwrap().value();
|
||||
wall.problems.extend(problems.iter().map(|p| p.uid));
|
||||
walls_table.insert(wall_uid, wall)?;
|
||||
|
||||
for problem in problems {
|
||||
let key = (wall_id, problem.uid);
|
||||
problems_table.insert(key, problem)?;
|
||||
}
|
||||
for problem in problems {
|
||||
let key = (wall_uid, problem.uid);
|
||||
problems_table.insert(key, problem)?;
|
||||
}
|
||||
write_txn.commit()?;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.await??;
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -88,6 +83,6 @@ pub(crate) async fn import_mini_moonboard_problems(config: &Config, db: Arc<Data
|
||||
#[derive(Debug, derive_more::Error, derive_more::Display, derive_more::From)]
|
||||
pub enum Error {
|
||||
Parser(moonboard_parser::Error),
|
||||
Redb(redb::Error),
|
||||
Tokio(tokio::task::JoinError),
|
||||
DbOperation(crate::server::db::DatabaseOperationError),
|
||||
}
|
||||
|
||||
@@ -4,15 +4,41 @@ use crate::models;
|
||||
use leptos::prelude::expect_context;
|
||||
use leptos::server;
|
||||
use server_fn::ServerFnError;
|
||||
use std::sync::Arc;
|
||||
use server_fn::error::ServerFnErrorErr;
|
||||
|
||||
#[server(
|
||||
input = Ron,
|
||||
output = Ron,
|
||||
custom = RonEncoded
|
||||
)]
|
||||
#[tracing::instrument(skip_all, err)]
|
||||
#[tracing::instrument(skip_all, err(Debug))]
|
||||
pub(crate) async fn get_walls() -> Result<RonEncoded<Vec<models::Wall>>, ServerFnError> {
|
||||
use crate::server::db::Database;
|
||||
use redb::ReadableTable;
|
||||
tracing::debug!("Enter");
|
||||
|
||||
let db = expect_context::<Database>();
|
||||
|
||||
let walls = db
|
||||
.read(|txn| {
|
||||
let walls_table = txn.open_table(crate::server::db::current::TABLE_WALLS)?;
|
||||
let walls: Vec<models::Wall> = walls_table.iter()?.map(|r| r.map(|(_, v)| v.value())).collect::<Result<_, _>>()?;
|
||||
Ok(walls)
|
||||
})
|
||||
.await?;
|
||||
|
||||
Ok(RonEncoded::new(walls))
|
||||
}
|
||||
|
||||
#[server(
|
||||
input = Ron,
|
||||
output = Ron,
|
||||
custom = RonEncoded
|
||||
)]
|
||||
#[tracing::instrument(skip_all, err(Debug))]
|
||||
pub(crate) async fn get_wall(wall_uid: models::WallUid) -> Result<RonEncoded<models::Wall>, ServerFnError> {
|
||||
use crate::server::db::Database;
|
||||
use crate::server::db::DatabaseOperationError;
|
||||
tracing::debug!("Enter");
|
||||
|
||||
#[derive(Debug, derive_more::Error, derive_more::Display)]
|
||||
@@ -21,17 +47,19 @@ pub(crate) async fn get_wall(wall_uid: models::WallUid) -> Result<RonEncoded<mod
|
||||
NotFound(#[error(not(source))] models::WallUid),
|
||||
}
|
||||
|
||||
let db = expect_context::<Arc<redb::Database>>();
|
||||
let db = expect_context::<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??;
|
||||
let wall = db
|
||||
.read(|txn| {
|
||||
let walls_table = txn.open_table(crate::server::db::current::TABLE_WALLS)?;
|
||||
let wall = walls_table
|
||||
.get(wall_uid)?
|
||||
.ok_or(Error::NotFound(wall_uid))
|
||||
.map_err(DatabaseOperationError::custom)?
|
||||
.value();
|
||||
Ok(wall)
|
||||
})
|
||||
.await?;
|
||||
|
||||
tracing::debug!("ok");
|
||||
|
||||
@@ -43,24 +71,80 @@ pub(crate) async fn get_wall(wall_uid: models::WallUid) -> Result<RonEncoded<mod
|
||||
output = Ron,
|
||||
custom = RonEncoded
|
||||
)]
|
||||
#[tracing::instrument(skip_all, err)]
|
||||
#[tracing::instrument(err(Debug))]
|
||||
pub(crate) async fn get_problems_for_wall(wall_uid: models::WallUid) -> Result<RonEncoded<Vec<models::Problem>>, ServerFnError> {
|
||||
use crate::server::db::Database;
|
||||
use crate::server::db::DatabaseOperationError;
|
||||
tracing::debug!("Enter");
|
||||
|
||||
#[derive(Debug, derive_more::Error, derive_more::Display, derive_more::From)]
|
||||
enum Error {
|
||||
#[display("Wall not found: {_0:?}")]
|
||||
WallNotFound(#[error(not(source))] models::WallUid),
|
||||
|
||||
DatabaseOperation(DatabaseOperationError),
|
||||
}
|
||||
|
||||
async fn inner(wall_uid: models::WallUid) -> Result<Vec<models::Problem>, Error> {
|
||||
let db = expect_context::<Database>();
|
||||
|
||||
let problems = db
|
||||
.read(|txn| {
|
||||
let walls_table = txn.open_table(crate::server::db::current::TABLE_WALLS)?;
|
||||
tracing::debug!("getting wall");
|
||||
let wall = walls_table
|
||||
.get(wall_uid)?
|
||||
.ok_or(Error::WallNotFound(wall_uid))
|
||||
.map_err(DatabaseOperationError::custom)?
|
||||
.value();
|
||||
tracing::debug!("got wall");
|
||||
drop(walls_table);
|
||||
|
||||
tracing::debug!("open problems table");
|
||||
let problems_table = txn.open_table(crate::server::db::current::TABLE_PROBLEMS)?;
|
||||
tracing::debug!("opened problems table");
|
||||
|
||||
let mut problems = Vec::new();
|
||||
for &problem_uid in &wall.problems {
|
||||
if let Some(problem) = problems_table.get((wall_uid, problem_uid))? {
|
||||
problems.push(problem.value());
|
||||
}
|
||||
}
|
||||
Ok(problems)
|
||||
})
|
||||
.await?;
|
||||
|
||||
Ok(problems)
|
||||
}
|
||||
|
||||
let problems = inner(wall_uid).await.map_err(error_reporter::Report::new).map_err(ServerFnError::new)?;
|
||||
|
||||
tracing::debug!("ok");
|
||||
|
||||
Ok(RonEncoded::new(problems))
|
||||
}
|
||||
|
||||
#[server(
|
||||
input = Ron,
|
||||
output = Ron,
|
||||
custom = RonEncoded
|
||||
)]
|
||||
#[tracing::instrument(skip_all, err(Debug))]
|
||||
pub(crate) async fn get_problem(
|
||||
wall_uid: models::WallUid,
|
||||
problem_uid: models::ProblemUid,
|
||||
) -> Result<RonEncoded<Option<models::Problem>>, ServerFnError> {
|
||||
use crate::server::db::Database;
|
||||
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??;
|
||||
let db = expect_context::<Database>();
|
||||
let problem = db
|
||||
.read(|txn| {
|
||||
let table = 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))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user