From c81d8bbfd46d53fba6e0086b5f859f8af8639f4a Mon Sep 17 00:00:00 2001 From: metamuffin Date: Wed, 2 Aug 2023 15:08:43 +0200 Subject: refactor admin panel --- server/src/import.rs | 43 ++++++++++++++++---------- server/src/main.rs | 17 ++++++---- server/src/routes/mod.rs | 5 +-- server/src/routes/ui/account/admin.rs | 58 ++++++++++++++++++++--------------- server/src/routes/ui/error.rs | 2 +- server/src/routes/ui/layout.rs | 9 ++++++ 6 files changed, 83 insertions(+), 51 deletions(-) (limited to 'server/src') diff --git a/server/src/import.rs b/server/src/import.rs index c8f4f10..b306332 100644 --- a/server/src/import.rs +++ b/server/src/import.rs @@ -5,27 +5,38 @@ */ use crate::{database::Database, CONF}; use anyhow::{bail, Context, Ok}; +use async_recursion::async_recursion; use jellycommon::Node; use log::info; -use std::{ffi::OsStr, fs::File, os::unix::prelude::OsStrExt, path::PathBuf}; +use std::{ffi::OsStr, fs::File, os::unix::prelude::OsStrExt, path::PathBuf, sync::LazyLock}; +use tokio::sync::Semaphore; -pub fn import(db: &Database) -> anyhow::Result<()> { +static IMPORT_SEM: LazyLock = LazyLock::new(|| Semaphore::new(1)); + +pub async fn import(db: &Database) -> anyhow::Result<()> { info!("clearing node tree"); - db.node.clear()?; - info!("importing..."); - import_path(CONF.library_path.clone(), db, None).context("indexing")?; + let permit = IMPORT_SEM.try_acquire()?; + { + db.node.clear()?; + info!("importing..."); + import_path(CONF.library_path.clone(), db, None) + .await + .context("indexing")?; + info!("import completed"); + } + drop(permit); Ok(()) } -pub fn import_path( +#[async_recursion] +pub async fn import_path( path: PathBuf, db: &Database, parent: Option, ) -> anyhow::Result> { if path.is_dir() { let mpath = path.join("directory.json"); - let children = path.read_dir()?.filter_map(|e| { - let e = e.unwrap(); + let children_paths = path.read_dir()?.map(Result::unwrap).filter_map(|e| { if e.path().extension() == Some(&OsStr::from_bytes(b"jelly")) || e.metadata().unwrap().is_dir() { @@ -35,23 +46,21 @@ pub fn import_path( } }); let identifier = path.file_name().unwrap().to_str().unwrap().to_string(); - let children = children - .map(|e| import_path(e, db, Some(identifier.clone()))) - .collect::>>()? - .into_iter() - .flatten() - .collect(); + let mut children_ids = Vec::new(); + for p in children_paths { + children_ids.extend(import_path(p, db, Some(identifier.clone())).await?) + } if mpath.exists() { let mut data: Node = serde_json::from_reader(File::open(mpath).context("metadata missing")?)?; - data.public.children = children; + data.public.children = children_ids; data.public.parent = parent; info!("insert {identifier}"); db.node.insert(&identifier, &data)?; Ok(vec![identifier]) } else { - Ok(children) + Ok(children_ids) } } else if path.is_file() { info!("loading item {path:?}"); @@ -65,7 +74,7 @@ pub fn import_path( .strip_suffix(".jelly") .unwrap() .to_string(); - + info!("insert {identifier}"); data.public.parent = parent; db.node.insert(&identifier, &data)?; diff --git a/server/src/main.rs b/server/src/main.rs index 341f72b..5b2d070 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -5,13 +5,12 @@ */ #![feature(lazy_cell)] -use std::fs::File; use database::Database; use jellycommon::config::GlobalConfig; use jellyremuxer::RemuxerContext; use once_cell::sync::Lazy; -use rocket::launch; use routes::build_rocket; +use std::fs::File; pub mod database; pub mod import; @@ -20,8 +19,7 @@ pub mod routes; pub static CONF: Lazy = Lazy::new(|| serde_json::from_reader(File::open("data/config.json").unwrap()).unwrap()); -#[launch] -fn rocket() -> _ { +fn main() { env_logger::builder() .filter_level(log::LevelFilter::Info) .parse_env("LOG") @@ -30,9 +28,16 @@ fn rocket() -> _ { #[cfg(feature = "bypass-auth")] log::warn!("authentification bypass enabled"); + tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build() + .unwrap() + .block_on(async_main()) +} +async fn async_main() { let remuxer = RemuxerContext::new(); let database = Database::open(&CONF.database_path).unwrap(); - import::import(&database).unwrap(); + import::import(&database).await.unwrap(); database.create_admin(); - build_rocket(remuxer, database) + build_rocket(remuxer, database).launch().await.unwrap(); } diff --git a/server/src/routes/mod.rs b/server/src/routes/mod.rs index 7f4789d..be4d2cb 100644 --- a/server/src/routes/mod.rs +++ b/server/src/routes/mod.rs @@ -18,8 +18,8 @@ use stream::r_stream; use ui::{ account::{ admin::{ - r_account_admin_dashboard, r_account_admin_invite, r_account_admin_remove_invite, - r_account_admin_remove_user, + r_account_admin_dashboard, r_account_admin_import, r_account_admin_invite, + r_account_admin_remove_invite, r_account_admin_remove_user, }, r_account_login, r_account_login_post, r_account_logout, r_account_logout_post, r_account_register, r_account_register_post, @@ -92,6 +92,7 @@ pub fn build_rocket(remuxer: RemuxerContext, database: Database) -> Rocket( + database: &Database, + flash: Option>, +) -> MyResult> { // TODO this doesnt scale, pagination! let users = database.user.iter().collect::, _>>()?; let invites = database.invite.iter().collect::, _>>()?; + let flash = flash.map(|f| f.map_err(|e| format!("{e:?}"))); Ok(LayoutPage { title: "Admin Dashboard".to_string(), content: markup::new! { h1 { "Admin Panel" } - h2 { "Invitations"} + @FlashDisplay { flash: flash.clone() } + h2 { "Library" } + form[method="POST", action=uri!(r_account_admin_import())] { + input[type="submit", value="(Re-)Import Library"]; + } + h2 { "Invitations" } form[method="POST", action=uri!(r_account_admin_invite())] { input[type="submit", value="Generate new invite code"]; } @@ -71,13 +84,7 @@ pub fn r_account_admin_invite( let i = format!("{}", rand::thread_rng().gen::()); database.invite.insert(&i, &())?; - Ok(LayoutPage { - title: "Admin Dashboard".to_string(), - content: markup::new! { - pre { code { @i } } - }, - ..Default::default() - }) + admin_dashboard(database, Some(Ok(format!("Invite: {}", i)))) } #[derive(FromForm)] @@ -99,14 +106,7 @@ pub fn r_account_admin_remove_user( .remove(&form.name)? .ok_or(anyhow!("user did not exist"))?; - Ok(LayoutPage { - title: "User removed".to_string(), - content: markup::new! { - p { "User removed" } - a[href=uri!(r_account_admin_dashboard())] {"Back"} - }, - ..Default::default() - }) + admin_dashboard(database, Some(Ok("User removed".into()))) } #[derive(FromForm)] @@ -128,12 +128,20 @@ pub fn r_account_admin_remove_invite( .remove(&form.invite)? .ok_or(anyhow!("invite did not exist"))?; - Ok(LayoutPage { - title: "Invite invalidated".to_string(), - content: markup::new! { - p { "Invite invalidated" } - a[href=uri!(r_account_admin_dashboard())] {"Back"} - }, - ..Default::default() - }) + admin_dashboard(database, Some(Ok("Invite invalidated".into()))) +} + +#[post("/account/admin/import")] +pub async fn r_account_admin_import( + session: Session, + database: &State, +) -> MyResult> { + if !session.user.admin { + Err(anyhow!("you not admin"))? + } + let r = import(&database).await; + admin_dashboard( + &database, + Some(r.map_err(|e| e.into()).map(|_| "Import successful".into())), + ) } diff --git a/server/src/routes/ui/error.rs b/server/src/routes/ui/error.rs index 01aebd3..190650f 100644 --- a/server/src/routes/ui/error.rs +++ b/server/src/routes/ui/error.rs @@ -90,4 +90,4 @@ impl From for MyError { fn from(err: serde_json::Error) -> Self { MyError(anyhow::anyhow!("{err}")) } -} \ No newline at end of file +} diff --git a/server/src/routes/ui/layout.rs b/server/src/routes/ui/layout.rs index 7844e22..7e51e5c 100644 --- a/server/src/routes/ui/layout.rs +++ b/server/src/routes/ui/layout.rs @@ -62,6 +62,15 @@ markup::define! { } } } + + FlashDisplay(flash: Option>) { + @if let Some(flash) = &flash { + @match flash { + Ok(mesg) => { section.message { p.success { @mesg } } } + Err(err) => { section.message { p.error { @format!("{err}") } } } + } + } + } } pub type DynLayoutPage<'a> = LayoutPage>; -- cgit v1.2.3-70-g09d2