pub mod methods; use chrono::{Datelike, SecondsFormat, Utc}; use dotenvy::dotenv; use rand::{distributions::Alphanumeric, thread_rng, Rng}; use sqlx::{ sqlite::{SqliteConnectOptions, SqlitePoolOptions}, Error, Pool, Sqlite, }; use std::{ env, str::FromStr, time::{SystemTime, UNIX_EPOCH}, }; use tide::prelude::*; #[derive(Debug, Deserialize, Serialize, sqlx::FromRow)] pub struct AccountWolves { pub id_wolves: String, pub id_student: String, pub email: String, pub expiry: String, pub name_first: String, pub name_second: String, } #[derive(Debug, Clone, Deserialize, Serialize, sqlx::FromRow)] pub struct AccountsNew { pub mail: String, pub auth_code: String, pub date_iso: String, pub date_expiry: String, pub name_first: String, pub name_surname: String, pub id_student: String, } #[derive(Debug, Clone, Deserialize, Serialize, sqlx::FromRow)] pub struct AccountsReset { pub user: String, pub auth_code: String, pub date_expiry: String, } #[derive(Debug, Clone, Deserialize, Serialize, sqlx::FromRow)] pub struct Accounts { pub user: String, pub uid: i64, pub discord: Option, pub mail: String, pub student_id: String, pub enabled: bool, pub secure: bool, } pub async fn db_init(config: &Config) -> Result, Error> { let database = format!("{}/{}", &config.home, &config.database); println!("Database: {:?}", &database); let pool = SqlitePoolOptions::new() .max_connections(5) .connect_with(SqliteConnectOptions::from_str(&format!("sqlite://{}", database))?.create_if_missing(true)) .await?; sqlx::query( "CREATE TABLE IF NOT EXISTS accounts_wolves ( id_wolves text primary key, id_student text not null, email text not null, expiry text not null, name_first text not null, name_second integer not null )", ) .execute(&pool) .await?; sqlx::query( "CREATE TABLE IF NOT EXISTS accounts_new ( mail text primary key, auth_code text not null, date_iso text not null, date_expiry text not null, name_first text not null, name_surname integer not null, id_student text not null )", ) .execute(&pool) .await?; sqlx::query("CREATE INDEX IF NOT EXISTS index_auth_code ON accounts_new (auth_code)") .execute(&pool) .await?; sqlx::query("CREATE INDEX IF NOT EXISTS index_date_expiry ON accounts_new (date_expiry)") .execute(&pool) .await?; sqlx::query( "CREATE TABLE IF NOT EXISTS accounts_reset ( user text primary key, auth_code text not null, date_expiry text not null )", ) .execute(&pool) .await?; sqlx::query("CREATE INDEX IF NOT EXISTS index_auth_code ON accounts_reset (auth_code)") .execute(&pool) .await?; sqlx::query("CREATE INDEX IF NOT EXISTS index_date_expiry ON accounts_reset (date_expiry)") .execute(&pool) .await?; // this is for active use sqlx::query( "CREATE TABLE IF NOT EXISTS accounts ( user text primary key, uid integer not null, discord text, mail text not null, student_id text not null, enabled integer not null, secure integer not null )", ) .execute(&pool) .await?; sqlx::query("CREATE INDEX IF NOT EXISTS index_uid_number ON accounts (uid)").execute(&pool).await?; sqlx::query("CREATE INDEX IF NOT EXISTS index_mail ON accounts (mail)").execute(&pool).await?; sqlx::query("CREATE INDEX IF NOT EXISTS index_student_id ON accounts (student_id)") .execute(&pool) .await?; Ok(pool) } pub fn get_now() -> i64 { if let Ok(x) = SystemTime::now().duration_since(UNIX_EPOCH) { x.as_secs() as i64 } else { 0 } } pub fn get_now_iso(short: bool) -> String { let now = Utc::now(); if short { format!("{}-{:02}-{:02}", now.year(), now.month(), now.day()) } else { now.to_rfc3339_opts(SecondsFormat::Millis, true) } } #[derive(Clone)] pub struct State { pub db: Pool, pub config: Config, } #[derive(Debug, Clone)] pub struct Config { pub ldap_host: String, pub ldap_admin: String, pub ldap_admin_pw: String, pub home: String, pub database: String, pub csv: String, pub host_port: String, pub mail_smtp: String, pub mail_user: String, pub mail_pass: String, } pub fn get_config() -> Config { dotenv().ok(); // reasonable defaults let mut config = Config { ldap_host: "".to_string(), ldap_admin: "".to_string(), ldap_admin_pw: "".to_string(), home: ".".to_string(), database: "database.db".to_string(), csv: "wolves.csv".to_string(), host_port: "127.0.0.1:8087".to_string(), mail_smtp: "".to_string(), mail_user: "".to_string(), mail_pass: "".to_string(), }; if let Ok(x) = env::var("LDAP_HOST") { config.ldap_host = x.trim().to_string(); } if let Ok(x) = env::var("LDAP_ADMIN") { config.ldap_admin = x.trim().to_string(); } if let Ok(x) = env::var("LDAP_ADMIN_PW") { config.ldap_admin_pw = x.trim().to_string(); } if let Ok(x) = env::var("HOME") { config.home = x.trim().to_string(); } if let Ok(x) = env::var("DATABASE") { config.database = x.trim().to_string(); } if let Ok(x) = env::var("CSV") { config.csv = x.trim().to_string(); } if let Ok(x) = env::var("HOST_PORT") { config.host_port = x.trim().to_string(); } if let Ok(x) = env::var("EMAIL_SMTP") { config.mail_smtp = x.trim().to_string(); } if let Ok(x) = env::var("EMAIL_USER") { config.mail_user = x.trim().to_string(); } if let Ok(x) = env::var("EMAIL_PASS") { config.mail_pass = x.trim().to_string(); } config } // from https://rust-lang-nursery.github.io/rust-cookbook/algorithms/randomness.html#create-random-passwords-from-a-set-of-alphanumeric-characters pub fn random_string(len: usize) -> String { thread_rng().sample_iter(&Alphanumeric).take(len).map(char::from).collect() } pub async fn get_wolves(db: &Pool) -> Vec { sqlx::query_as::<_, AccountWolves>( r#" SELECT * FROM accounts_wolves "#, ) .fetch_all(db) .await .unwrap_or(vec![]) }