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