mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-02 08:28:18 +00:00
Database seed tool
This commit is contained in:
1 parent
d07a147db9
commit
aac31f77ad
14 files changed
+266
No files matched your search
Generated
+18
@@ -1798,6 +1798,22 @@ dependencies = [
|
||||
"opaque-debug",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dev-database-seed"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"config",
|
||||
"easyenv",
|
||||
"enums",
|
||||
"errors",
|
||||
"hashing",
|
||||
"log",
|
||||
"mysql_queries",
|
||||
"sqlx",
|
||||
"tokens",
|
||||
"tokio 1.25.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "diesel"
|
||||
version = "1.4.6"
|
||||
@@ -2538,8 +2554,10 @@ dependencies = [
|
||||
name = "hashing"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"bcrypt",
|
||||
"data-encoding",
|
||||
"errors",
|
||||
"md-5",
|
||||
"ring 0.17.0-alpha.11",
|
||||
]
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ members = [
|
||||
# CLI tools for operations, etc.
|
||||
"crates/cli/database_backup",
|
||||
"crates/cli/database_cleanup",
|
||||
"crates/cli/dev_database_seed",
|
||||
"crates/cli/scrape_test",
|
||||
|
||||
# Shared libraries
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
[package]
|
||||
name = "dev-database-seed"
|
||||
edition = "2018"
|
||||
version = "0.0.1"
|
||||
authors = [
|
||||
"Brandon Thomas <bt@brand.io>",
|
||||
"Brandon Thomas <echelon@gmail.com>",
|
||||
]
|
||||
publish = false
|
||||
|
||||
[[bin]]
|
||||
name = "dev-database-seed"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
# Internal
|
||||
config = { path = "../../lib/deprecated/config" }
|
||||
easyenv = { path = "../../lib/easyenv" }
|
||||
enums = { path = "../../_types/enums" }
|
||||
errors = { path = "../../lib/errors" }
|
||||
hashing = { path = "../../lib/files/hashing" }
|
||||
mysql_queries = { path = "../../lib/mysql_queries" }
|
||||
tokens = { path = "../../_types/tokens" }
|
||||
|
||||
# External
|
||||
log = "0.4.14"
|
||||
|
||||
# Runtime
|
||||
# NB: We switched to Tokio from `async-std` for broader ecosystem support.
|
||||
tokio = { version = "1.6.0", features = ["macros"] }
|
||||
|
||||
# MySql Database
|
||||
# "offline" supports typesafe builds without a live database
|
||||
# "rustls" doesn't appear to work with IP addresses, per:
|
||||
# https://github.com/launchbadge/sqlx/issues/1095#issuecomment-798050828
|
||||
sqlx = { version = "0.6.2", features = [ "mysql", "runtime-actix-native-tls", "offline", "chrono" ] }
|
||||
|
||||
[dev-dependencies]
|
||||
# None yet
|
||||
@@ -0,0 +1,33 @@
|
||||
use log::{info, log};
|
||||
use sqlx::mysql::MySqlPoolOptions;
|
||||
|
||||
use config::shared_constants::{DEFAULT_MYSQL_CONNECTION_STRING, DEFAULT_RUST_LOG};
|
||||
use errors::AnyhowResult;
|
||||
|
||||
use crate::seeding::users::seed_user_accounts;
|
||||
use crate::seeding::zero_shot_tts::seed_zero_shot_tts;
|
||||
|
||||
pub mod seeding;
|
||||
|
||||
#[tokio::main]
|
||||
pub async fn main() -> AnyhowResult<()> {
|
||||
easyenv::init_all_with_default_logging(Some(DEFAULT_RUST_LOG));
|
||||
|
||||
info!("Database seed CLI script.");
|
||||
|
||||
let db_connection_string =
|
||||
easyenv::get_env_string_or_default(
|
||||
"MYSQL_URL",
|
||||
DEFAULT_MYSQL_CONNECTION_STRING);
|
||||
|
||||
let pool = MySqlPoolOptions::new()
|
||||
.max_connections(easyenv::get_env_num("MYSQL_MAX_CONNECTIONS", 3)?)
|
||||
.connect(&db_connection_string)
|
||||
.await?;
|
||||
|
||||
seed_user_accounts(&pool).await?;
|
||||
seed_zero_shot_tts(&pool).await?;
|
||||
|
||||
info!("Done!");
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
pub mod users;
|
||||
pub mod zero_shot_tts;
|
||||
@@ -0,0 +1,52 @@
|
||||
use log::{info, warn};
|
||||
use sqlx::{MySql, Pool};
|
||||
|
||||
use errors::{anyhow, AnyhowResult};
|
||||
use hashing::bcrypt::bcrypt_password_hash::bcrypt_password_hash;
|
||||
use hashing::md5::email_to_gravatar_hash::email_to_gravatar_hash;
|
||||
use mysql_queries::queries::users::user::create_account::{create_account, CreateAccountArgs};
|
||||
|
||||
pub async fn seed_user_accounts(mysql_pool: &Pool<MySql>) -> AnyhowResult<()> {
|
||||
info!("Seeding user accounts...");
|
||||
|
||||
let users = [
|
||||
("admin", "admin@storyteller.ai", "password"),
|
||||
("hanashi", "admin@storyteller.ai", "password"),
|
||||
("test", "admin@storyteller.ai", "password"),
|
||||
];
|
||||
|
||||
for (username, email, password) in users {
|
||||
let result = seed_user(username, email, password, &mysql_pool).await;
|
||||
match result {
|
||||
Ok(_) => info!("Seeded {}", username),
|
||||
Err(err) => warn!("Could not seed user {} : {:?}", username, err),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn seed_user(
|
||||
username: &str,
|
||||
email_address: &str,
|
||||
password: &str,
|
||||
mysql_pool: &Pool<MySql>,
|
||||
) -> AnyhowResult<()> {
|
||||
info!("Seeding user {} ...", username);
|
||||
|
||||
let display_name = username.clone();
|
||||
let username = username.to_lowercase();
|
||||
let email_gravatar_hash = email_to_gravatar_hash(&email_address);
|
||||
let password_hash = bcrypt_password_hash(password)?;
|
||||
|
||||
create_account(mysql_pool, CreateAccountArgs {
|
||||
username: &username,
|
||||
display_name,
|
||||
email_address,
|
||||
email_gravatar_hash: &email_gravatar_hash,
|
||||
password_hash: &password_hash,
|
||||
ip_address: "127.0.0.1",
|
||||
}).await.map_err(|err| anyhow!("err: {:?}", err))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
use log::{info, warn};
|
||||
use sqlx::{MySql, Pool};
|
||||
|
||||
use errors::{anyhow, AnyhowResult};
|
||||
use hashing::bcrypt::bcrypt_password_hash::bcrypt_password_hash;
|
||||
use hashing::md5::email_to_gravatar_hash::email_to_gravatar_hash;
|
||||
use mysql_queries::queries::users::user::create_account::{create_account, CreateAccountArgs};
|
||||
|
||||
pub async fn seed_zero_shot_tts(mysql_pool: &Pool<MySql>) -> AnyhowResult<()> {
|
||||
info!("Seeding zero shot TTS...");
|
||||
|
||||
let users = [
|
||||
("admin", "admin@storyteller.ai", "password"),
|
||||
("hanashi", "admin@storyteller.ai", "password"),
|
||||
("test", "admin@storyteller.ai", "password"),
|
||||
];
|
||||
|
||||
for (username, email, password) in users {
|
||||
let result = seed_user(username, email, password, &mysql_pool).await;
|
||||
match result {
|
||||
Ok(_) => info!("Seeded {}", username),
|
||||
Err(err) => warn!("Could not seed user {} : {:?}", username, err),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn seed_user(
|
||||
username: &str,
|
||||
email_address: &str,
|
||||
password: &str,
|
||||
mysql_pool: &Pool<MySql>,
|
||||
) -> AnyhowResult<()> {
|
||||
info!("Seeding user {} ...", username);
|
||||
|
||||
let display_name = username.clone();
|
||||
let username = username.to_lowercase();
|
||||
let email_gravatar_hash = email_to_gravatar_hash(&email_address);
|
||||
let password_hash = bcrypt_password_hash(password)?;
|
||||
|
||||
create_account(mysql_pool, CreateAccountArgs {
|
||||
username: &username,
|
||||
display_name,
|
||||
email_address,
|
||||
email_gravatar_hash: &email_gravatar_hash,
|
||||
password_hash: &password_hash,
|
||||
ip_address: "127.0.0.1",
|
||||
}).await.map_err(|err| anyhow!("err: {:?}", err))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -18,7 +18,9 @@ path = "src/lib.rs"
|
||||
errors = { path = "../../errors" }
|
||||
|
||||
# External
|
||||
bcrypt = "0.8" # password hashes
|
||||
data-encoding = "2.3.2"
|
||||
md-5 = "0.9.1" # DO NOT USE FOR SERIOUS CRYPTO (we use this for gravatar)
|
||||
ring = "0.17.0-alpha.10"
|
||||
|
||||
[dev-dependencies]
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
use errors::AnyhowResult;
|
||||
|
||||
/// Create a bcrypt password hash.
|
||||
/// TODO(bt,2023-10-05): Investigate stronger hashing (Argon2??)
|
||||
/// TODO(bt,2023-10-05): Return something better than an AnyhowResult<()> !
|
||||
pub fn bcrypt_password_hash(password: &str) -> AnyhowResult<String> {
|
||||
let password_hash = bcrypt_lib::hash(password, bcrypt_lib::DEFAULT_COST)?;
|
||||
Ok(password_hash)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::bcrypt::bcrypt_password_hash::bcrypt_password_hash;
|
||||
|
||||
#[test]
|
||||
fn test_bcrypt_password_hashing() {
|
||||
let password = "password";
|
||||
for _ in 0..3 {
|
||||
// NB(1): Each bcrypt hash has an os-random salt
|
||||
// NB(2): Testing these takes time (as bcrypt is meant to), so we only test 3.
|
||||
let password_hash = bcrypt_password_hash(&password).unwrap();
|
||||
assert!(bcrypt_lib::verify(password, &password_hash).unwrap());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
pub mod bcrypt_password_hash;
|
||||
@@ -18,4 +18,18 @@
|
||||
#![allow(dead_code)]
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
// TODO(bt,2023-10-05): Should this library contain any "user libraries"
|
||||
// (like password hashing and gravatar), or focus on purely hashing functions?
|
||||
|
||||
// NB: Because we have our own bcrypt module
|
||||
extern crate bcrypt as bcrypt_lib;
|
||||
|
||||
pub mod bcrypt;
|
||||
|
||||
// NB(bt,2023-10-05): md5 is broken and should not be used for anything seriously.
|
||||
// We're using it in this case to support email gravatar hashing, which honestly,
|
||||
// should be carefully reconsidered since all of these hashes are broken and exposed
|
||||
// to the public.
|
||||
pub mod md5;
|
||||
|
||||
pub mod sha256;
|
||||
@@ -0,0 +1,25 @@
|
||||
use md5::{Digest, Md5};
|
||||
|
||||
/// See https://gravatar.com/
|
||||
pub fn email_to_gravatar_hash(email_address: &str) -> String {
|
||||
let email = email_address.trim().to_lowercase();
|
||||
|
||||
let mut hasher = Md5::new();
|
||||
hasher.update(email);
|
||||
let hash = hasher.finalize();
|
||||
let gravatar_hash = format!("{:x}", hash);
|
||||
|
||||
gravatar_hash
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::md5::email_to_gravatar_hash::email_to_gravatar_hash;
|
||||
|
||||
#[test]
|
||||
fn test_email_hashing() {
|
||||
assert_eq!(email_to_gravatar_hash("email@email.com"), "4f64c9f81bb0d4ee969aaf7b4a5a6f40".to_string());
|
||||
assert_eq!(email_to_gravatar_hash("foo@bar.com"), "f3ada405ce890b6f8204094deb12d8a8".to_string());
|
||||
assert_eq!(email_to_gravatar_hash("brandon@storyteller.ai"), "54bd9d9491cd63f733f0970ae4059438".to_string());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
pub mod email_to_gravatar_hash;
|
||||
@@ -23,6 +23,7 @@ pub struct CreateAccountSuccessResult {
|
||||
pub user_id: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum CreateAccountError {
|
||||
EmailIsTaken,
|
||||
UsernameIsTaken,
|
||||
|
||||
Reference in new issue
Block a user