#![feature(const_vec_string_slice)] use data_encoding::HEXUPPER; use rand::RngCore; use tokio::sync::OnceCell; pub mod asset; pub mod auth; pub mod format; pub mod request; pub mod result; pub mod ui; // TODO : CORS? // Postgres // TODO : Background Jobs // TODO : Refactor caching // TODO : mail // TODO : scheduled jobs // TODO : IDEA // more efficient table join using WHERE ANY instead of multiple SELECTs // map_tables(Vec, Fn(&T) -> U) -> Vec pub static PG: OnceCell = OnceCell::const_new(); /// A macro to retrieve or initialize the `PostgreSQL` connection pool. /// /// This macro provides a convenient way to access the `PgPool`. If the pool is not already initialized, /// it creates a new pool using the connection string from the `$DATABASE_URL` environment variable. /// /// # Example /// ```ignore /// use based::get_pg; /// /// let pool = get_pg!(); /// ``` #[macro_export] macro_rules! get_pg { () => { if let Some(client) = $crate::PG.get() { client } else { let client = sqlx::postgres::PgPoolOptions::new() .max_connections(12) .connect(&std::env::var("DATABASE_URL").unwrap()) .await .unwrap(); $crate::PG.set(client).unwrap(); $crate::PG.get().unwrap() } }; } pub fn gen_random(token_length: usize) -> String { let mut token_bytes = vec![0u8; token_length]; rand::thread_rng().fill_bytes(&mut token_bytes); HEXUPPER.encode(&token_bytes) } fn transpose(matrix: Vec>) -> Vec> { if matrix.is_empty() { return vec![]; } let row_len = matrix[0].len(); let mut transposed = vec![Vec::with_capacity(matrix.len()); row_len]; for row in matrix { for (i, elem) in row.into_iter().enumerate() { transposed[i].push(elem); } } transposed } // TODO : More types #[derive(Clone)] pub enum DatabaseType { TEXT(String), INTEGER(i32), } impl DatabaseType { pub fn type_name(&self) -> &str { match self { Self::TEXT(_) => "TEXT", Self::INTEGER(_) => "INTEGER", } } pub fn as_integer(self) -> i32 { match self { DatabaseType::INTEGER(result) => return result, _ => panic!("Wrong DB type"), } } pub fn as_text(self) -> String { match self { DatabaseType::TEXT(result) => return result, _ => panic!("Wrong DB type"), } } } pub async fn batch_insert(table: &str, values: &[String], entries: Vec>) { assert_eq!(values.len(), entries.first().unwrap().len()); let mut cmd = format!("INSERT INTO {table} ("); cmd.push_str(&values.join(", ")); cmd.push_str(") SELECT * FROM UNNEST("); let entries = transpose(entries); for i in 0..values.len() { let t = entries.get(i).unwrap().first().unwrap().type_name(); if i == (entries.len() - 1) { cmd.push_str(&format!("${}::{t}[]", i + 1)); } else { cmd.push_str(&format!("${}::{t}[],", i + 1)); } } cmd.push_str(")"); log::debug!("Executing batch query: {cmd}"); let mut query = sqlx::query(&cmd); for e in entries { let first = e.first().unwrap(); match first { DatabaseType::TEXT(_) => { query = query.bind(e.into_iter().map(|x| x.as_text()).collect::>()); } DatabaseType::INTEGER(_) => { query = query.bind(e.into_iter().map(|x| x.as_integer()).collect::>()); } } } query.execute(get_pg!()).await.unwrap(); } // TODO : LibraryIndex // index, cleanup, add_one, remove_one, get, exists, query