diff --git a/src/config/mod.rs b/src/config/mod.rs index 7eba8e48f23fd1cb50953f130c3a36e68a3edb1d..ff7f51766f887008c36e7d9835a0d9a03ef06cd7 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -3,6 +3,7 @@ pub mod structs; use crate::file::{self, exists}; use colored::Colorize; use macros_rs::fmt::{crashln, string}; +use pickledb::SerializationMethod; use std::fs; use structs::{Config, Settings}; @@ -36,4 +37,15 @@ pub fn read() -> Config { impl Config { pub fn get_address(&self) -> (String, u16) { (self.settings.address.clone(), self.settings.port.clone()) } + + pub fn kv_serialization_method(&self) -> Option { + let database = self.database.clone()?.kv?; + + match &*database.method { + "json" | "default" => Some(SerializationMethod::Json), + "yaml" | "yml" => Some(SerializationMethod::Yaml), + "binary" | "bin" => Some(SerializationMethod::Bin), + _ => Some(SerializationMethod::Bin), + } + } } diff --git a/src/database.rs b/src/database.rs new file mode 100644 index 0000000000000000000000000000000000000000..7701409c8f40f19a0a225e2c83708b10c070f93c --- /dev/null +++ b/src/database.rs @@ -0,0 +1,8 @@ +pub mod kv; +pub use kv::*; + +pub mod mongo; +pub use mongo::*; + +pub mod redis; +pub use redis::*; diff --git a/src/database/kv.rs b/src/database/kv.rs index 8b358763d22f75d5d3ed43696c68c995c59e3c74..350e03a851db8fa662bed8baca455ade0bdb492f 100644 --- a/src/database/kv.rs +++ b/src/database/kv.rs @@ -1,18 +1,74 @@ use crate::config; -use pickledb::{PickleDb, PickleDbDumpPolicy, SerializationMethod}; - -pub fn load(path: String) -> PickleDb { - let config = config::read().database.unwrap(); - let method = match config.kv.unwrap().method.as_str() { - "json" | "default" => SerializationMethod::Json, - "yaml" | "yml" => SerializationMethod::Yaml, - "cbor" | "conbin" => SerializationMethod::Bin, - "binary" | "bin" => SerializationMethod::Bin, - _ => SerializationMethod::Bin, - }; - - PickleDb::new(path, PickleDbDumpPolicy::AutoDump, method) + +use macros_rs::{fmt::string, fs::file_exists}; +use pickledb::{PickleDb, PickleDbDumpPolicy}; +use rhai::{plugin::*, FnNamespace}; +use std::cell::RefCell; + +fn load(path: String) -> Option { + let config = config::read(); + + if !file_exists!(&path) { + PickleDb::new(&path, PickleDbDumpPolicy::AutoDump, config.kv_serialization_method()?); + } + + match PickleDb::load(path, PickleDbDumpPolicy::AutoDump, config.kv_serialization_method()?) { + Ok(db) => Some(db), + Err(_) => None, + } } -// use load to not erase db on every load // add .iter() method + +#[export_module] +pub mod kv_db { + #[derive(Clone)] + pub struct KV<'s> { + pub db: &'s RefCell, + } + + pub fn load<'s>(path: String) -> KV<'s> { + // add error handling with error messages + let db = RefCell::new(super::load(path).unwrap()); + KV { db: Box::leak(Box::new(db)) } + } + + #[rhai_fn(global, pure, return_raw)] + pub fn set(conn: &mut KV, key: String, value: String) -> Result<(), Box> { + let mut db = conn.db.borrow_mut(); + match db.set(&key, &value) { + Ok(_) => Ok(()), + Err(err) => Err(err.to_string().into()), + } + } + + #[rhai_fn(global)] + pub fn get(conn: KV, key: String) -> String { + let db = conn.db.borrow(); + match db.get::(&key) { + Some(data) => data, + None => string!(""), + } + } + + #[rhai_fn(global, pure)] + pub fn del(conn: &mut KV, key: String) -> bool { + let mut db = conn.db.borrow_mut(); + match db.rem(&key) { + Ok(bool) => bool, + Err(_) => false, + } + } + + #[rhai_fn(global)] + pub fn exists(conn: KV, key: String) -> bool { conn.db.borrow().exists(&key) } + + #[rhai_fn(global)] + pub fn list(conn: KV) -> Vec { conn.db.borrow().get_all() } + + #[rhai_fn(global)] + pub fn count(conn: KV) -> i64 { conn.db.borrow().total_keys() as i64 } + + #[rhai_fn(global, name = "drop")] + pub fn drop_db(conn: KV) { drop(conn.db.borrow()); } +} diff --git a/src/database/mod.rs b/src/database/mod.rs deleted file mode 100644 index 05c6d5b95ef33f9ffcfe34f0e75e1c5279ced62e..0000000000000000000000000000000000000000 --- a/src/database/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod kv; diff --git a/src/database/mongo.rs b/src/database/mongo.rs new file mode 100644 index 0000000000000000000000000000000000000000..a1a8352002ce239c61339c9ba7fcdcc140916781 --- /dev/null +++ b/src/database/mongo.rs @@ -0,0 +1,254 @@ +use crate::{config, helpers::collection_exists}; + +use rhai::{plugin::*, serde::to_dynamic, Array, FnNamespace}; +use serde::{Deserialize, Serialize}; +use std::sync::Arc; + +use mongodb::{ + bson::{doc, Document}, + results::{CollectionSpecification, DeleteResult, InsertOneResult, UpdateResult}, + sync::{Client as MongoClient, Collection, Cursor, Database}, +}; + +#[export_module] +pub mod mongo_db { + #[derive(Clone)] + pub struct Client { + pub client: Option, + } + + #[derive(Clone)] + pub struct Mongo { + pub db: Option, + } + + #[derive(Clone, Debug, Deserialize, Serialize)] + pub struct MongoDynamic(Dynamic); + + unsafe impl Send for MongoDynamic {} + unsafe impl Sync for MongoDynamic {} + + trait MongoVec { + fn into_vec(self) -> Vec; + } + + trait MongoDocument { + fn into_doc(self) -> Document; + fn into_map(self) -> MongoDynamic; + } + + impl Into for Dynamic { + fn into(self) -> MongoDynamic { MongoDynamic(self) } + } + + impl FromIterator for Array { + fn from_iter>(iter: I) -> Self { iter.into_iter().map(|m| m.0).collect() } + } + + impl MongoVec for Array { + fn into_vec(self) -> Vec { self.into_iter().map(|m| m.into_map()).collect() } + } + + impl MongoDocument for Dynamic { + fn into_doc(self) -> Document { + Document::from( + serde_json::from_str(&match serde_json::to_string(&self) { + Ok(data) => data, + Err(err) => format!("{{\"err\": \"{err}\"}}"), + }) + .unwrap_or(doc! {"err": format!("unable to deserialize {self}")}), + ) + } + fn into_map(self) -> MongoDynamic { MongoDynamic(to_dynamic(self.into_doc()).unwrap()) } + } + + pub fn connect() -> Client { + let config = config::read().database.unwrap(); + match MongoClient::with_uri_str(config.mongo.unwrap().server.unwrap_or("".to_string())) { + Ok(client) => Client { client: Some(client) }, + Err(_) => Client { client: None }, + } + } + + pub fn shutdown(conn: Client) { conn.client.unwrap().shutdown(); } + + #[rhai_fn(global, return_raw, name = "list")] + pub fn list_databases(conn: Client) -> Result> { + match conn.client { + Some(client) => match client.list_databases(None, None) { + Err(err) => Err(err.to_string().into()), + Ok(list) => to_dynamic(list), + }, + None => to_dynamic::(vec![]), + } + } + + #[rhai_fn(global, return_raw, name = "count")] + pub fn count_databases(conn: Client) -> Result> { + match conn.client { + Some(client) => match client.list_databases(None, None) { + Err(err) => Err(err.to_string().into()), + Ok(list) => Ok(list.len() as i64), + }, + None => Ok(0), + } + } + + #[rhai_fn(global)] + pub fn db(conn: Client, name: String) -> Mongo { + match conn.client { + None => Mongo { db: None }, + Some(client) => Mongo { db: Some(client.database(&name)) }, + } + } + + #[rhai_fn(global, return_raw, name = "list")] + pub fn list_collections(m: Mongo) -> Result> { + match m.db { + Some(client) => match client.list_collections(None, None) { + Err(err) => Err(err.to_string().into()), + Ok(list) => to_dynamic(list.map(|item| item.unwrap()).collect::>()), + }, + None => to_dynamic::(vec![]), + } + } + + #[rhai_fn(global, return_raw, name = "get")] + pub fn collection(m: Mongo, name: String) -> Result, Box> { + match m.db { + Some(client) => Ok(client.collection(&name)), + None => Err("No database found".into()), + } + } + + #[rhai_fn(global, return_raw, name = "create")] + pub fn create_collection(m: Mongo, name: String) -> Result, Box> { + match m.db { + Some(client) => match collection_exists(&client, &name).unwrap() { + true => Ok(client.collection(&name)), + false => match client.create_collection(&name, None) { + Err(err) => Err(err.to_string().into()), + Ok(_) => Ok(client.collection(&name)), + }, + }, + None => Err("No database found".into()), + } + } + + #[rhai_fn(global, return_raw, name = "count")] + pub fn count_collections(collection: Collection) -> Result> { + match collection.count_documents(None, None) { + Ok(count) => Ok(count as i64), + Err(err) => Err(err.to_string().into()), + } + } + + #[rhai_fn(global, return_raw, name = "find")] + pub fn find_all(collection: Collection) -> Result>, Box> { + match collection.find(None, None) { + Ok(cursor) => Ok(Arc::new(cursor)), + Err(err) => Err(err.to_string().into()), + } + } + + #[rhai_fn(global, return_raw, name = "find_one")] + pub fn find_one(collection: Collection, filter: Dynamic) -> Result> { + match collection.find_one(filter.into_doc(), None) { + Ok(cursor) => match cursor { + Some(item) => to_dynamic::(item), + None => to_dynamic::>(vec![]), + }, + Err(err) => Err(err.to_string().into()), + } + } + + #[rhai_fn(global, return_raw, name = "find")] + pub fn find_filter(collection: Collection, filter: Dynamic) -> Result>, Box> { + match collection.find(filter.into_doc(), None) { + Ok(cursor) => Ok(Arc::new(cursor)), + Err(err) => Err(err.to_string().into()), + } + } + + #[rhai_fn(global, name = "count")] + pub fn count_cursor(cursor: Arc>) -> i64 { + match Arc::into_inner(cursor) { + Some(cursor) => cursor.count() as i64, + None => 0, + } + } + + #[rhai_fn(global, name = "count")] + pub fn count_collect(items: Array) -> i64 { items.iter().count() as i64 } + + #[rhai_fn(global, return_raw, name = "collect")] + pub fn collect(cursor: Arc>) -> Result> { + match Arc::into_inner(cursor) { + Some(cursor) => match cursor.collect() { + Ok(items) => to_dynamic::(items), + Err(err) => Err(err.to_string().into()), + }, + None => to_dynamic::(vec![]), + } + } + + #[rhai_fn(global, name = "drop")] + pub fn drop_collection(collection: Collection) -> bool { + match collection.drop(None) { + Ok(_) => true, + Err(_) => false, + } + } + + #[rhai_fn(global, return_raw, name = "drop")] + pub fn drop_database(m: Mongo) -> Result> { + match m.db { + Some(client) => match client.drop(None) { + Ok(_) => Ok(true), + Err(err) => Err(err.to_string().into()), + }, + None => Err("No collection found".into()), + } + } + + #[rhai_fn(global, return_raw, name = "insert")] + pub fn insert_one(collection: Collection, map: Dynamic) -> Result> { + match collection.insert_one(map.into_map(), None) { + Ok(res) => to_dynamic::(res), + Err(err) => Err(err.to_string().into()), + } + } + + #[rhai_fn(global, return_raw, name = "insert")] + pub fn insert_many(collection: Collection, map: Array) -> Result> { + match collection.insert_many(map.into_vec(), None) { + Ok(res) => Ok(res.inserted_ids.into_iter().map(|(_, value)| to_dynamic(value).unwrap()).collect::()), + Err(err) => Err(err.to_string().into()), + } + } + + #[rhai_fn(global, return_raw)] + pub fn delete(collection: Collection, map: Dynamic) -> Result> { + match collection.delete_one(map.into_doc(), None) { + Ok(res) => to_dynamic::(res), + Err(err) => Err(err.to_string().into()), + } + } + + #[rhai_fn(global, return_raw)] + pub fn delete_many(collection: Collection, map: Dynamic) -> Result> { + match collection.delete_many(map.into_doc(), None) { + Ok(res) => to_dynamic::(res), + Err(err) => Err(err.to_string().into()), + } + } + + #[rhai_fn(global, return_raw)] + pub fn update(collection: Collection, query: Dynamic, replacement: Dynamic) -> Result> { + let replacement: MongoDynamic = replacement.into(); + match collection.replace_one(query.into_doc(), replacement, None) { + Ok(res) => to_dynamic::(res), + Err(err) => Err(err.to_string().into()), + } + } +} diff --git a/src/database/redis.rs b/src/database/redis.rs new file mode 100644 index 0000000000000000000000000000000000000000..99c2bba6c4855bec415cbbee1cbb386a22dd4301 --- /dev/null +++ b/src/database/redis.rs @@ -0,0 +1,179 @@ +use crate::config; + +use macros_rs::fmt::string; +use redis::{Client as RedisClient, Commands}; +use rhai::{plugin::*, serde::to_dynamic, FnNamespace}; +use std::collections::BTreeMap; + +#[export_module] +pub mod redis_db { + #[derive(Clone)] + pub struct Redis { + pub client: Option, + } + + pub fn connect() -> Redis { + let config = config::read().database.unwrap(); + match RedisClient::open(config.redis.unwrap().server) { + Ok(client) => Redis { client: Some(client) }, + Err(_) => Redis { client: None }, + } + } + + #[rhai_fn(global, return_raw, name = "set")] + pub fn set_string(redis: Redis, key: String, value: String) -> Result<(), Box> { + let mut conn = redis.client.unwrap().get_connection().unwrap(); + match conn.set::(key, value) { + Ok(_) => Ok(()), + Err(err) => Err(err.to_string().into()), + } + } + + #[rhai_fn(global, return_raw, name = "set")] + pub fn set_i64(redis: Redis, key: String, value: i64) -> Result<(), Box> { + let mut conn = redis.client.unwrap().get_connection().unwrap(); + match conn.set::(key, value) { + Ok(_) => Ok(()), + Err(err) => Err(err.to_string().into()), + } + } + + #[rhai_fn(global, name = "get")] + pub fn get(redis: Redis, key: String) -> String { + let mut conn = redis.client.unwrap().get_connection().unwrap(); + match conn.get::(key) { + Ok(data) => data, + Err(_) => string!(""), + } + } + + #[rhai_fn(global)] + pub fn del(redis: Redis, key: String) -> String { + let mut conn = redis.client.unwrap().get_connection().unwrap(); + match conn.del(key) { + Ok(data) => data, + Err(err) => err.to_string(), + } + } + + #[rhai_fn(global)] + pub fn expire(redis: Redis, key: String, s: i64) -> String { + let mut conn = redis.client.unwrap().get_connection().unwrap(); + match conn.expire(key, s) { + Ok(data) => data, + Err(err) => err.to_string(), + } + } + + #[rhai_fn(global)] + pub fn persist(redis: Redis, key: String) -> String { + let mut conn = redis.client.unwrap().get_connection().unwrap(); + match conn.persist(key) { + Ok(data) => data, + Err(err) => err.to_string(), + } + } + + #[rhai_fn(global)] + pub fn ttl(redis: Redis, key: String) -> String { + let mut conn = redis.client.unwrap().get_connection().unwrap(); + match conn.ttl(key) { + Ok(data) => data, + Err(err) => err.to_string(), + } + } + + #[rhai_fn(global)] + pub fn rename(redis: Redis, key: String, new: String) -> String { + let mut conn = redis.client.unwrap().get_connection().unwrap(); + match conn.rename(key, new) { + Ok(data) => data, + Err(err) => err.to_string(), + } + } + + #[rhai_fn(global)] + pub fn append(redis: Redis, key: String, value: String) -> String { + let mut conn = redis.client.unwrap().get_connection().unwrap(); + match conn.append(key, value) { + Ok(data) => data, + Err(err) => err.to_string(), + } + } + + #[rhai_fn(global)] + pub fn inc(redis: Redis, key: String, value: i64) -> String { + let mut conn = redis.client.unwrap().get_connection().unwrap(); + match conn.incr(key, value) { + Ok(data) => data, + Err(err) => err.to_string(), + } + } + + #[rhai_fn(global)] + pub fn dec(redis: Redis, key: String, value: i64) -> String { + let mut conn = redis.client.unwrap().get_connection().unwrap(); + match conn.decr(key, value) { + Ok(data) => data, + Err(err) => err.to_string(), + } + } + + #[rhai_fn(global)] + pub fn exists(redis: Redis, key: String) -> bool { + let mut conn = redis.client.unwrap().get_connection().unwrap(); + match conn.exists(key) { + Ok(bool) => bool, + Err(_) => false, + } + } + + #[rhai_fn(global, return_raw)] + pub fn keys(redis: Redis, filter: String) -> Result> { + let mut conn = redis.client.unwrap().get_connection().unwrap(); + match conn.keys(filter) { + Ok(data) => to_dynamic::>(data), + Err(_) => to_dynamic::>(vec![]), + } + } + + #[rhai_fn(global, return_raw, name = "list")] + pub fn list_all(redis: Redis) -> Result> { + let mut conn = redis.client.unwrap().get_connection().unwrap(); + + let keys = match conn.keys("*") { + Ok(data) => data, + Err(_) => vec![], + }; + + let items = keys + .into_iter() + .map(|key| { + let value: Option = conn.get(&key).unwrap(); + (key, value.unwrap_or_else(|| "".to_string())) + }) + .collect::>(); + + to_dynamic(items) + } + + #[rhai_fn(global, return_raw, name = "list")] + pub fn list_filter(redis: Redis, filter: String) -> Result> { + let mut conn = redis.client.unwrap().get_connection().unwrap(); + + let keys = match conn.keys(filter) { + Ok(data) => data, + Err(_) => vec![], + }; + + let items = keys + .into_iter() + .map(|key| { + let value: Option = conn.get(&key).unwrap(); + (key, value.unwrap_or_else(|| "".to_string())) + }) + .collect::>(); + + to_dynamic(items) + } +} diff --git a/src/helpers.rs b/src/helpers.rs index c5a12abc49bd09ccb402d6a56859fa515461c655..6fa7b31848c5c182b193da309fb07783ef61c50b 100644 --- a/src/helpers.rs +++ b/src/helpers.rs @@ -1,6 +1,7 @@ use actix_web::http::StatusCode; +use mongodb::{bson::doc, sync::Database}; use regex::{Captures, Regex}; -use rhai::{Engine, ParseError, AST}; +use rhai::{plugin::EvalAltResult, Engine, ParseError, AST}; pub fn rm_first(s: &str) -> &str { let mut chars = s.chars(); @@ -28,6 +29,15 @@ pub fn route_to_fn(input: &str) -> String { ) } +pub fn collection_exists(d: &Database, name: &String) -> Result> { + let filter = doc! { "name": &name }; + + match d.list_collection_names(Some(filter)) { + Err(err) => Err(err.to_string().into()), + Ok(list) => Ok(list.into_iter().any(|col| col == *name)), + } +} + pub fn convert_status(code: i64) -> StatusCode { let u16_code = code as u16; StatusCode::from_u16(u16_code).unwrap_or(StatusCode::OK) diff --git a/src/main.rs b/src/main.rs index d85739a1d10407bce1ffc662ee4462a60e9eaf33..f0160a576614515ef0f128bdefc1d177a2b30cb2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,31 +2,26 @@ mod config; mod database; mod file; mod helpers; +mod modules; + +use modules::prelude::*; use askama::Template; use config::structs::Config; use lazy_static::lazy_static; use mime::Mime; -use pickledb::PickleDb; -use redis::{Client as RedisClient, Commands}; + use regex::{Captures, Error, Regex}; use reqwest::blocking::Client as ReqwestClient; -use serde::{Deserialize, Serialize}; use smartstring::alias::String as SmString; -use std::{cell::RefCell, collections::BTreeMap, fs, sync::Arc}; +use std::{collections::BTreeMap, fs}; use tracing_bunyan_formatter::{BunyanFormattingLayer, JsonStorageLayer}; use tracing_subscriber::{filter::LevelFilter, prelude::*}; -use rhai::{packages::Package, plugin::*, serde::to_dynamic, Array, Dynamic, Engine, FnNamespace, Map, Scope}; +use rhai::{packages::Package, plugin::*, Dynamic, Engine, Map, Scope}; use rhai_fs::FilesystemPackage; use rhai_url::UrlPackage; -use mongodb::{ - bson::{doc, Document}, - results::{CollectionSpecification, DeleteResult, InsertOneResult, UpdateResult}, - sync::{Client as MongoClient, Collection, Cursor, Database}, -}; - use macros_rs::{ exp::ternary, fmt::{crashln, str, string}, @@ -121,15 +116,6 @@ fn match_route(route_template: &str, placeholders: &[&str], url: &str) -> Option Some(matched_placeholders) } -fn collection_exists(d: &Database, name: &String) -> Result> { - let filter = doc! { "name": &name }; - - match d.list_collection_names(Some(filter)) { - Err(err) => Err(err.to_string().into()), - Ok(list) => Ok(list.into_iter().any(|col| col == *name)), - } -} - fn match_segment(route_segment: &str, url_segment: &str, placeholders: &[&str]) -> Option { if route_segment.starts_with('{') && route_segment.ends_with('}') { let placeholder = &route_segment[1..route_segment.len() - 1]; @@ -151,657 +137,6 @@ fn match_segment(route_segment: &str, url_segment: &str, placeholders: &[&str]) } } -#[export_module] -mod default { - pub fn text(string: String) -> (String, ContentType, StatusCode) { (string, ContentType::plaintext(), StatusCode::OK) } - pub fn html(string: String) -> (String, ContentType, StatusCode) { (string, ContentType::html(), StatusCode::OK) } - pub fn json(object: Dynamic) -> (String, ContentType, StatusCode) { - match serde_json::to_string(&object) { - Ok(result) => (result, ContentType::json(), StatusCode::OK), - Err(err) => (err.to_string(), ContentType::plaintext(), StatusCode::INTERNAL_SERVER_ERROR), - } - } -} - -#[export_module] -mod status { - pub fn text(string: String, status: i64) -> (String, ContentType, StatusCode) { (string, ContentType::plaintext(), helpers::convert_status(status)) } - pub fn html(string: String, status: i64) -> (String, ContentType, StatusCode) { (string, ContentType::html(), helpers::convert_status(status)) } - pub fn json(object: Dynamic, status: i64) -> (String, ContentType, StatusCode) { - match serde_json::to_string(&object) { - Ok(result) => (result, ContentType::json(), helpers::convert_status(status)), - Err(err) => (err.to_string(), ContentType::plaintext(), StatusCode::INTERNAL_SERVER_ERROR), - } - } -} - -#[export_module] -mod json { - pub fn dump<'s>(object: Dynamic) -> String { - match serde_json::to_string(&object) { - Ok(result) => result, - Err(err) => err.to_string(), - } - } - - #[rhai_fn(global, return_raw, name = "parse")] - pub fn parse<'s>(json: String) -> Result> { - match serde_json::from_str(&json) { - Ok(map) => Ok(map), - Err(err) => Err(err.to_string().into()), - } - } -} - -#[export_module] -mod mongo_db { - #[derive(Clone)] - pub struct Client { - pub client: Option, - } - - #[derive(Clone)] - pub struct Mongo { - pub db: Option, - } - - #[derive(Clone, Debug, Deserialize, Serialize)] - pub struct MongoDynamic(Dynamic); - - unsafe impl Send for MongoDynamic {} - unsafe impl Sync for MongoDynamic {} - - trait MongoVec { - fn into_vec(self) -> Vec; - } - - trait MongoDocument { - fn into_doc(self) -> Document; - fn into_map(self) -> MongoDynamic; - } - - impl Into for Dynamic { - fn into(self) -> MongoDynamic { MongoDynamic(self) } - } - - impl FromIterator for Array { - fn from_iter>(iter: I) -> Self { iter.into_iter().map(|m| m.0).collect() } - } - - impl MongoVec for Array { - fn into_vec(self) -> Vec { self.into_iter().map(|m| m.into_map()).collect() } - } - - impl MongoDocument for Dynamic { - fn into_doc(self) -> Document { - Document::from( - serde_json::from_str(&match serde_json::to_string(&self) { - Ok(data) => data, - Err(err) => format!("{{\"err\": \"{err}\"}}"), - }) - .unwrap_or(doc! {"err": format!("unable to deserialize {self}")}), - ) - } - fn into_map(self) -> MongoDynamic { MongoDynamic(to_dynamic(self.into_doc()).unwrap()) } - } - - pub fn connect() -> Client { - let config = config::read().database.unwrap(); - match MongoClient::with_uri_str(config.mongo.unwrap().server.unwrap_or("".to_string())) { - Ok(client) => Client { client: Some(client) }, - Err(_) => Client { client: None }, - } - } - - pub fn shutdown(conn: Client) { conn.client.unwrap().shutdown(); } - - #[rhai_fn(global, return_raw, name = "list")] - pub fn list_databases(conn: Client) -> Result> { - match conn.client { - Some(client) => match client.list_databases(None, None) { - Err(err) => Err(err.to_string().into()), - Ok(list) => to_dynamic(list), - }, - None => to_dynamic::(vec![]), - } - } - - #[rhai_fn(global, return_raw, name = "count")] - pub fn count_databases(conn: Client) -> Result> { - match conn.client { - Some(client) => match client.list_databases(None, None) { - Err(err) => Err(err.to_string().into()), - Ok(list) => Ok(list.len() as i64), - }, - None => Ok(0), - } - } - - #[rhai_fn(global)] - pub fn db(conn: Client, name: String) -> Mongo { - match conn.client { - None => Mongo { db: None }, - Some(client) => Mongo { db: Some(client.database(&name)) }, - } - } - - #[rhai_fn(global, return_raw, name = "list")] - pub fn list_collections(m: Mongo) -> Result> { - match m.db { - Some(client) => match client.list_collections(None, None) { - Err(err) => Err(err.to_string().into()), - Ok(list) => to_dynamic(list.map(|item| item.unwrap()).collect::>()), - }, - None => to_dynamic::(vec![]), - } - } - - #[rhai_fn(global, return_raw, name = "get")] - pub fn collection(m: Mongo, name: String) -> Result, Box> { - match m.db { - Some(client) => Ok(client.collection(&name)), - None => Err("No database found".into()), - } - } - - #[rhai_fn(global, return_raw, name = "create")] - pub fn create_collection(m: Mongo, name: String) -> Result, Box> { - match m.db { - Some(client) => match collection_exists(&client, &name).unwrap() { - true => Ok(client.collection(&name)), - false => match client.create_collection(&name, None) { - Err(err) => Err(err.to_string().into()), - Ok(_) => Ok(client.collection(&name)), - }, - }, - None => Err("No database found".into()), - } - } - - #[rhai_fn(global, return_raw, name = "count")] - pub fn count_collections(collection: Collection) -> Result> { - match collection.count_documents(None, None) { - Ok(count) => Ok(count as i64), - Err(err) => Err(err.to_string().into()), - } - } - - #[rhai_fn(global, return_raw, name = "find")] - pub fn find_all(collection: Collection) -> Result>, Box> { - match collection.find(None, None) { - Ok(cursor) => Ok(Arc::new(cursor)), - Err(err) => Err(err.to_string().into()), - } - } - - #[rhai_fn(global, return_raw, name = "find_one")] - pub fn find_one(collection: Collection, filter: Dynamic) -> Result> { - match collection.find_one(filter.into_doc(), None) { - Ok(cursor) => match cursor { - Some(item) => to_dynamic::(item), - None => to_dynamic::>(vec![]), - }, - Err(err) => Err(err.to_string().into()), - } - } - - #[rhai_fn(global, return_raw, name = "find")] - pub fn find_filter(collection: Collection, filter: Dynamic) -> Result>, Box> { - match collection.find(filter.into_doc(), None) { - Ok(cursor) => Ok(Arc::new(cursor)), - Err(err) => Err(err.to_string().into()), - } - } - - #[rhai_fn(global, name = "count")] - pub fn count_cursor(cursor: Arc>) -> i64 { - match Arc::into_inner(cursor) { - Some(cursor) => cursor.count() as i64, - None => 0, - } - } - - #[rhai_fn(global, name = "count")] - pub fn count_collect(items: Array) -> i64 { items.iter().count() as i64 } - - #[rhai_fn(global, return_raw, name = "collect")] - pub fn collect(cursor: Arc>) -> Result> { - match Arc::into_inner(cursor) { - Some(cursor) => match cursor.collect() { - Ok(items) => to_dynamic::(items), - Err(err) => Err(err.to_string().into()), - }, - None => to_dynamic::(vec![]), - } - } - - #[rhai_fn(global, name = "drop")] - pub fn drop_collection(collection: Collection) -> bool { - match collection.drop(None) { - Ok(_) => true, - Err(_) => false, - } - } - - #[rhai_fn(global, return_raw, name = "drop")] - pub fn drop_database(m: Mongo) -> Result> { - match m.db { - Some(client) => match client.drop(None) { - Ok(_) => Ok(true), - Err(err) => Err(err.to_string().into()), - }, - None => Err("No collection found".into()), - } - } - - #[rhai_fn(global, return_raw, name = "insert")] - pub fn insert_one(collection: Collection, map: Dynamic) -> Result> { - match collection.insert_one(map.into_map(), None) { - Ok(res) => to_dynamic::(res), - Err(err) => Err(err.to_string().into()), - } - } - - #[rhai_fn(global, return_raw, name = "insert")] - pub fn insert_many(collection: Collection, map: Array) -> Result> { - match collection.insert_many(map.into_vec(), None) { - Ok(res) => Ok(res.inserted_ids.into_iter().map(|(_, value)| to_dynamic(value).unwrap()).collect::()), - Err(err) => Err(err.to_string().into()), - } - } - - #[rhai_fn(global, return_raw)] - pub fn delete(collection: Collection, map: Dynamic) -> Result> { - match collection.delete_one(map.into_doc(), None) { - Ok(res) => to_dynamic::(res), - Err(err) => Err(err.to_string().into()), - } - } - - #[rhai_fn(global, return_raw)] - pub fn delete_many(collection: Collection, map: Dynamic) -> Result> { - match collection.delete_many(map.into_doc(), None) { - Ok(res) => to_dynamic::(res), - Err(err) => Err(err.to_string().into()), - } - } - - #[rhai_fn(global, return_raw)] - pub fn update(collection: Collection, query: Dynamic, replacement: Dynamic) -> Result> { - let replacement: MongoDynamic = replacement.into(); - match collection.replace_one(query.into_doc(), replacement, None) { - Ok(res) => to_dynamic::(res), - Err(err) => Err(err.to_string().into()), - } - } -} - -#[export_module] -mod kv_db { - #[derive(Clone)] - pub struct KV<'s> { - pub db: &'s RefCell, - } - - pub fn load<'s>(path: String) -> KV<'s> { - let db = RefCell::new(database::kv::load(path)); - KV { db: Box::leak(Box::new(db)) } - } - - #[rhai_fn(global, pure, return_raw)] - pub fn set(conn: &mut KV, key: String, value: String) -> Result<(), Box> { - let mut db = conn.db.borrow_mut(); - match db.set(&key, &value) { - Ok(_) => Ok(()), - Err(err) => Err(err.to_string().into()), - } - } - - #[rhai_fn(global)] - pub fn get(conn: KV, key: String) -> String { - let db = conn.db.borrow(); - match db.get::(&key) { - Some(data) => data, - None => string!(""), - } - } - - #[rhai_fn(global, pure)] - pub fn del(conn: &mut KV, key: String) -> bool { - let mut db = conn.db.borrow_mut(); - match db.rem(&key) { - Ok(bool) => bool, - Err(_) => false, - } - } - - #[rhai_fn(global)] - pub fn exists(conn: KV, key: String) -> bool { conn.db.borrow().exists(&key) } - - #[rhai_fn(global)] - pub fn list(conn: KV) -> Vec { conn.db.borrow().get_all() } - - #[rhai_fn(global)] - pub fn count(conn: KV) -> i64 { conn.db.borrow().total_keys() as i64 } - - #[rhai_fn(global, name = "drop")] - pub fn drop_db(conn: KV) { drop(conn.db.borrow()); } -} - -#[export_module] -mod redis_db { - #[derive(Clone)] - pub struct Redis { - pub client: Option, - } - - pub fn connect() -> Redis { - let config = config::read().database.unwrap(); - match RedisClient::open(config.redis.unwrap().server) { - Ok(client) => Redis { client: Some(client) }, - Err(_) => Redis { client: None }, - } - } - - #[rhai_fn(global, return_raw, name = "set")] - pub fn set_string(redis: Redis, key: String, value: String) -> Result<(), Box> { - let mut conn = redis.client.unwrap().get_connection().unwrap(); - match conn.set::(key, value) { - Ok(_) => Ok(()), - Err(err) => Err(err.to_string().into()), - } - } - - #[rhai_fn(global, return_raw, name = "set")] - pub fn set_i64(redis: Redis, key: String, value: i64) -> Result<(), Box> { - let mut conn = redis.client.unwrap().get_connection().unwrap(); - match conn.set::(key, value) { - Ok(_) => Ok(()), - Err(err) => Err(err.to_string().into()), - } - } - - #[rhai_fn(global, name = "get")] - pub fn get(redis: Redis, key: String) -> String { - let mut conn = redis.client.unwrap().get_connection().unwrap(); - match conn.get::(key) { - Ok(data) => data, - Err(_) => string!(""), - } - } - - #[rhai_fn(global)] - pub fn del(redis: Redis, key: String) -> String { - let mut conn = redis.client.unwrap().get_connection().unwrap(); - match conn.del(key) { - Ok(data) => data, - Err(err) => err.to_string(), - } - } - - #[rhai_fn(global)] - pub fn expire(redis: Redis, key: String, s: i64) -> String { - let mut conn = redis.client.unwrap().get_connection().unwrap(); - match conn.expire(key, s) { - Ok(data) => data, - Err(err) => err.to_string(), - } - } - - #[rhai_fn(global)] - pub fn persist(redis: Redis, key: String) -> String { - let mut conn = redis.client.unwrap().get_connection().unwrap(); - match conn.persist(key) { - Ok(data) => data, - Err(err) => err.to_string(), - } - } - - #[rhai_fn(global)] - pub fn ttl(redis: Redis, key: String) -> String { - let mut conn = redis.client.unwrap().get_connection().unwrap(); - match conn.ttl(key) { - Ok(data) => data, - Err(err) => err.to_string(), - } - } - - #[rhai_fn(global)] - pub fn rename(redis: Redis, key: String, new: String) -> String { - let mut conn = redis.client.unwrap().get_connection().unwrap(); - match conn.rename(key, new) { - Ok(data) => data, - Err(err) => err.to_string(), - } - } - - #[rhai_fn(global)] - pub fn append(redis: Redis, key: String, value: String) -> String { - let mut conn = redis.client.unwrap().get_connection().unwrap(); - match conn.append(key, value) { - Ok(data) => data, - Err(err) => err.to_string(), - } - } - - #[rhai_fn(global)] - pub fn inc(redis: Redis, key: String, value: i64) -> String { - let mut conn = redis.client.unwrap().get_connection().unwrap(); - match conn.incr(key, value) { - Ok(data) => data, - Err(err) => err.to_string(), - } - } - - #[rhai_fn(global)] - pub fn dec(redis: Redis, key: String, value: i64) -> String { - let mut conn = redis.client.unwrap().get_connection().unwrap(); - match conn.decr(key, value) { - Ok(data) => data, - Err(err) => err.to_string(), - } - } - - #[rhai_fn(global)] - pub fn exists(redis: Redis, key: String) -> bool { - let mut conn = redis.client.unwrap().get_connection().unwrap(); - match conn.exists(key) { - Ok(bool) => bool, - Err(_) => false, - } - } - - #[rhai_fn(global, return_raw)] - pub fn keys(redis: Redis, filter: String) -> Result> { - let mut conn = redis.client.unwrap().get_connection().unwrap(); - match conn.keys(filter) { - Ok(data) => to_dynamic::>(data), - Err(_) => to_dynamic::>(vec![]), - } - } - - #[rhai_fn(global, return_raw, name = "list")] - pub fn list_all(redis: Redis) -> Result> { - let mut conn = redis.client.unwrap().get_connection().unwrap(); - - let keys = match conn.keys("*") { - Ok(data) => data, - Err(_) => vec![], - }; - - let items = keys - .into_iter() - .map(|key| { - let value: Option = conn.get(&key).unwrap(); - (key, value.unwrap_or_else(|| "".to_string())) - }) - .collect::>(); - - to_dynamic(items) - } - - #[rhai_fn(global, return_raw, name = "list")] - pub fn list_filter(redis: Redis, filter: String) -> Result> { - let mut conn = redis.client.unwrap().get_connection().unwrap(); - - let keys = match conn.keys(filter) { - Ok(data) => data, - Err(_) => vec![], - }; - - let items = keys - .into_iter() - .map(|key| { - let value: Option = conn.get(&key).unwrap(); - (key, value.unwrap_or_else(|| "".to_string())) - }) - .collect::>(); - - to_dynamic(items) - } -} - -#[export_module] -mod http { - #[derive(Clone)] - pub struct Http { - pub length: Option, - pub status: u16, - pub err: Option, - pub body: Option, - } - - impl From for Map { - fn from(http: Http) -> Self { - let mut map = Map::new(); - map.insert(SmString::from("status"), Dynamic::from(http.status as i64)); - - if let Some(length) = http.length { - map.insert(SmString::from("length"), Dynamic::from(length as i64)); - } - if let Some(err) = http.err { - map.insert(SmString::from("err"), Dynamic::from(err)); - } - if let Some(body) = http.body { - map.insert(SmString::from("body"), Dynamic::from(body)); - } - - return map; - } - } - - pub fn get(url: String) -> Http { - let client = ReqwestClient::new(); - let response = match client.get(url).send() { - Ok(res) => res, - Err(err) => { - return Http { - length: Some(0), - status: 0, - err: Some(err.to_string()), - body: None, - } - } - }; - - if response.status().is_success() { - Http { - length: response.content_length(), - status: response.status().as_u16(), - err: None, - body: Some(response.text().unwrap()), - } - } else { - Http { - length: response.content_length(), - status: response.status().as_u16(), - err: Some(response.text().unwrap()), - body: None, - } - } - } - - pub fn post(url: String, data: Map) -> Http { - let client = ReqwestClient::new(); - - let data = match serde_json::to_string(&data) { - Ok(result) => result, - Err(err) => err.to_string(), - }; - - let response = match client.post(url).body(data).send() { - Ok(res) => res, - Err(err) => { - return Http { - length: Some(0), - status: 0, - err: Some(err.to_string()), - body: None, - } - } - }; - - if response.status().is_success() { - Http { - length: response.content_length(), - status: response.status().as_u16(), - err: None, - body: Some(response.text().unwrap()), - } - } else { - Http { - length: response.content_length(), - status: response.status().as_u16(), - err: Some(response.text().unwrap()), - body: None, - } - } - } - - #[rhai_fn(get = "status")] - pub fn status(res: Http) -> i64 { res.status as i64 } - - #[rhai_fn(global, pure, return_raw, name = "raw")] - pub fn raw(res: &mut Http) -> Result> { Ok(res.clone().into()) } - - #[rhai_fn(get = "length")] - pub fn length(res: Http) -> i64 { - match res.length { - Some(len) => len as i64, - None => 0, - } - } - - #[rhai_fn(get = "body", return_raw)] - pub fn body(res: Http) -> Result> { - match res.body { - Some(body) => Ok(body.to_string()), - None => Ok(string!("")), - } - } - - #[rhai_fn(global, pure, return_raw, name = "json")] - pub fn json(res: &mut Http) -> Result> { - let body = str!(res.body.clone().unwrap()); - match serde_json::from_str(body) { - Ok(map) => Ok(map), - Err(err) => Err(err.to_string().into()), - } - } - - #[rhai_fn(get = "error", return_raw)] - pub fn error(res: Http) -> Result> { - let err = match res.err { - Some(err) => format!("\"{err}\""), - None => string!("null"), - }; - match serde_json::from_str(&format!("{{\"message\":{err}}}")) { - Ok(msg) => Ok(msg), - Err(err) => Err(err.to_string().into()), - } - } -} - async fn handler(req: HttpRequest, config: Data) -> impl Responder { let url = match req.uri().to_string().strip_prefix("/") { Some(url) => url.to_string(), diff --git a/src/modules.rs b/src/modules.rs new file mode 100644 index 0000000000000000000000000000000000000000..8b6d1946f511f5d9df891502f721f4765a1fa118 --- /dev/null +++ b/src/modules.rs @@ -0,0 +1,10 @@ +pub mod http; +pub mod parse; +pub mod response; + +pub mod prelude { + pub use super::http::*; + pub use super::parse::*; + pub use super::response::*; + pub use crate::database::*; +} diff --git a/src/modules/http.rs b/src/modules/http.rs new file mode 100644 index 0000000000000000000000000000000000000000..5ae1bd297f496a053dceeb7a32c6f7bb0a2a0dcf --- /dev/null +++ b/src/modules/http.rs @@ -0,0 +1,145 @@ +use macros_rs::fmt::{str, string}; +use reqwest::blocking::Client as ReqwestClient; +use rhai::{plugin::*, FnNamespace, Map}; +use smartstring::alias::String as SmString; + +#[export_module] +pub mod http { + #[derive(Clone)] + pub struct Http { + pub length: Option, + pub status: u16, + pub err: Option, + pub body: Option, + } + + impl From for Map { + fn from(http: Http) -> Self { + let mut map = Map::new(); + map.insert(SmString::from("status"), Dynamic::from(http.status as i64)); + + if let Some(length) = http.length { + map.insert(SmString::from("length"), Dynamic::from(length as i64)); + } + if let Some(err) = http.err { + map.insert(SmString::from("err"), Dynamic::from(err)); + } + if let Some(body) = http.body { + map.insert(SmString::from("body"), Dynamic::from(body)); + } + + return map; + } + } + + pub fn get(url: String) -> Http { + let client = ReqwestClient::new(); + let response = match client.get(url).send() { + Ok(res) => res, + Err(err) => { + return Http { + length: Some(0), + status: 0, + err: Some(err.to_string()), + body: None, + } + } + }; + + if response.status().is_success() { + Http { + length: response.content_length(), + status: response.status().as_u16(), + err: None, + body: Some(response.text().unwrap()), + } + } else { + Http { + length: response.content_length(), + status: response.status().as_u16(), + err: Some(response.text().unwrap()), + body: None, + } + } + } + + pub fn post(url: String, data: Map) -> Http { + let client = ReqwestClient::new(); + + let data = match serde_json::to_string(&data) { + Ok(result) => result, + Err(err) => err.to_string(), + }; + + let response = match client.post(url).body(data).send() { + Ok(res) => res, + Err(err) => { + return Http { + length: Some(0), + status: 0, + err: Some(err.to_string()), + body: None, + } + } + }; + + if response.status().is_success() { + Http { + length: response.content_length(), + status: response.status().as_u16(), + err: None, + body: Some(response.text().unwrap()), + } + } else { + Http { + length: response.content_length(), + status: response.status().as_u16(), + err: Some(response.text().unwrap()), + body: None, + } + } + } + + #[rhai_fn(get = "status")] + pub fn status(res: Http) -> i64 { res.status as i64 } + + #[rhai_fn(global, pure, return_raw, name = "raw")] + pub fn raw(res: &mut Http) -> Result> { Ok(res.clone().into()) } + + #[rhai_fn(get = "length")] + pub fn length(res: Http) -> i64 { + match res.length { + Some(len) => len as i64, + None => 0, + } + } + + #[rhai_fn(get = "body", return_raw)] + pub fn body(res: Http) -> Result> { + match res.body { + Some(body) => Ok(body.to_string()), + None => Ok(string!("")), + } + } + + #[rhai_fn(global, pure, return_raw, name = "json")] + pub fn json(res: &mut Http) -> Result> { + let body = str!(res.body.clone().unwrap()); + match serde_json::from_str(body) { + Ok(map) => Ok(map), + Err(err) => Err(err.to_string().into()), + } + } + + #[rhai_fn(get = "error", return_raw)] + pub fn error(res: Http) -> Result> { + let err = match res.err { + Some(err) => format!("\"{err}\""), + None => string!("null"), + }; + match serde_json::from_str(&format!("{{\"message\":{err}}}")) { + Ok(msg) => Ok(msg), + Err(err) => Err(err.to_string().into()), + } + } +} diff --git a/src/modules/parse.rs b/src/modules/parse.rs new file mode 100644 index 0000000000000000000000000000000000000000..b668b52f33fc11a648591f3122ea1dfcaf9e316a --- /dev/null +++ b/src/modules/parse.rs @@ -0,0 +1,19 @@ +use rhai::{plugin::*, Map}; + +#[export_module] +pub mod json { + pub fn dump<'s>(object: Dynamic) -> String { + match serde_json::to_string(&object) { + Ok(result) => result, + Err(err) => err.to_string(), + } + } + + #[rhai_fn(global, return_raw, name = "parse")] + pub fn parse<'s>(json: String) -> Result> { + match serde_json::from_str(&json) { + Ok(map) => Ok(map), + Err(err) => Err(err.to_string().into()), + } + } +} diff --git a/src/modules/response.rs b/src/modules/response.rs new file mode 100644 index 0000000000000000000000000000000000000000..cfe9cba600072a8df6fd91e088ab201a2863cf8c --- /dev/null +++ b/src/modules/response.rs @@ -0,0 +1,32 @@ +use crate::helpers::convert_status; + +use actix_web::http::{header::ContentType, StatusCode}; +use rhai::plugin::*; + +#[export_module] +pub mod default { + pub fn text(string: String) -> (String, ContentType, StatusCode) { (string, ContentType::plaintext(), StatusCode::OK) } + + pub fn html(string: String) -> (String, ContentType, StatusCode) { (string, ContentType::html(), StatusCode::OK) } + + pub fn json(object: Dynamic) -> (String, ContentType, StatusCode) { + match serde_json::to_string(&object) { + Ok(result) => (result, ContentType::json(), StatusCode::OK), + Err(err) => (err.to_string(), ContentType::plaintext(), StatusCode::INTERNAL_SERVER_ERROR), + } + } +} + +#[export_module] +pub mod status { + pub fn text(string: String, status: i64) -> (String, ContentType, StatusCode) { (string, ContentType::plaintext(), convert_status(status)) } + + pub fn html(string: String, status: i64) -> (String, ContentType, StatusCode) { (string, ContentType::html(), convert_status(status)) } + + pub fn json(object: Dynamic, status: i64) -> (String, ContentType, StatusCode) { + match serde_json::to_string(&object) { + Ok(result) => (result, ContentType::json(), convert_status(status)), + Err(err) => (err.to_string(), ContentType::plaintext(), StatusCode::INTERNAL_SERVER_ERROR), + } + } +}