153 lines
3.7 KiB
Rust
153 lines
3.7 KiB
Rust
#![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<T>, Fn(&T) -> U) -> Vec<U>
|
|
|
|
pub static PG: OnceCell<sqlx::PgPool> = 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<T: Clone>(matrix: Vec<Vec<T>>) -> Vec<Vec<T>> {
|
|
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<Vec<DatabaseType>>) {
|
|
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::<Vec<_>>());
|
|
}
|
|
DatabaseType::INTEGER(_) => {
|
|
query = query.bind(e.into_iter().map(|x| x.as_integer()).collect::<Vec<_>>());
|
|
}
|
|
}
|
|
}
|
|
|
|
query.execute(get_pg!()).await.unwrap();
|
|
}
|
|
|
|
// TODO : LibraryIndex
|
|
// index, cleanup, add_one, remove_one, get, exists, query
|