#![allow(proc_macro_derive_resolution_fallback)]
extern crate structopt;
#[macro_use]
extern crate diesel;
extern crate dotenv;
#[macro_use]
extern crate serde;
#[macro_use]
extern crate serde_json;
#[macro_use]
extern crate serde_derive;
#[macro_use]
extern crate diesel_migrations;
#[macro_use]
extern crate fake;
extern crate indicatif;
extern crate rand;
use std::fs::File;
use std::io::prelude::*;
use diesel::pg::PgConnection;
use diesel::prelude::*;
use dotenv::dotenv;
use std::env;
use std::process::Command;
use std::error::Error;
use std::io;
use std::io::ErrorKind::InvalidInput;
use structopt::StructOpt;
use indicatif::{ProgressBar, ProgressStyle};
pub mod db;
pub mod models;
mod schema;
pub mod sql_functions;
use db::*;
use pg_pool::Pool;
embed_migrations!("./migrations");
#[derive(StructOpt, Debug)]
#[structopt(
name = "birdseed",
about = "The libellis database seeder",
long_about = "You can use birdseed to seed a libellis db with junk data!"
)]
pub enum Birdseed {
#[structopt(name = "feed")]
Feed {
#[structopt(short = "r", long = "rows", default_value = "1000")]
row_count: u32,
},
#[structopt(name = "icecream")]
Icecream {
#[structopt(short = "r", long = "rows", default_value = "1000")]
row_count: u32,
},
#[structopt(name = "fences")]
Fences {
#[structopt(short = "f", long = "file", default_value = "data/fences.json")]
filepath: String,
},
#[structopt(name = "setup")]
Setup,
#[structopt(name = "migrate")]
Migrate {
#[structopt(short = "d", long = "database", default_value = "all")]
db: String,
},
#[structopt(name = "rebuild")]
Rebuild {
#[structopt(short = "d", long = "database", default_value = "all")]
db: String,
},
#[structopt(name = "clear")]
Clear,
}
pub fn run(config: Birdseed) -> Result<(), Box<dyn Error>> {
match config {
Birdseed::Feed { row_count } => populate_all(row_count),
Birdseed::Rebuild { db } => rebuild(&db),
Birdseed::Setup => setup(),
Birdseed::Icecream { row_count } => populate_icecream(row_count),
Birdseed::Fences { filepath } => load_fences(filepath),
Birdseed::Migrate { db } => migrate(&db),
Birdseed::Clear => clear_all(),
}
}
fn setup() -> Result<(), Box<dyn Error>> {
drop_database("libellis");
drop_database("libellis_test");
println!("\r\n 🐦 Creating Main Database 🐦\r\n",);
setup_database("libellis");
println!("\r\n 🐦 Creating Test Database 🐦\r\n",);
setup_database("libellis_test");
println!("\r\n 🐦 Running Main DB Migrations 🐦\r\n",);
rebuild("main")?;
println!("\r\n 🐦 Running Test DB Migrations 🐦\r\n",);
rebuild("test")?;
println!("\r\n 🐦 All Done! 🐦\r\n",);
Ok(())
}
fn setup_database(database: &str) {
Command::new("createdb")
.arg(database)
.output()
.expect("failed to create database");
}
fn drop_database(database: &str) {
Command::new("dropdb")
.arg(database)
.output()
.expect("failed to drop database");
}
fn load_fences(filepath: String) -> Result<(), Box<dyn Error>> {
use std::io::BufReader;
dotenv().ok();
let base_url = env::var("PSQL_URL")?;
env::set_var("DATABASE_URL", &format!("{}{}", base_url, "libellis"));
let pool = pg_pool::generate_pool();
let file = File::open(filepath)?;
let mut buf_reader = BufReader::new(file);
let mut contents = String::new();
buf_reader.read_to_string(&mut contents)?;
fences::load_geo_data(&pool, &contents)?;
Ok(())
}
fn populate_all(row_count: u32) -> Result<(), Box<dyn Error>> {
dotenv().ok();
let base_url = env::var("PSQL_URL")?;
env::set_var("DATABASE_URL", &format!("{}{}", base_url, "libellis"));
let pool = pg_pool::generate_pool();
println!("\r\n 🐦 Seeding All Tables 🐦\r\n",);
let bar = ProgressBar::new((row_count * 11) as u64);
bar.set_style(
ProgressStyle::default_bar()
.template("[{elapsed_precise}] {bar:40.cyan/blue} {msg}")
.progress_chars("##-"),
);
let usernames = users::populate(&pool, row_count, &bar)?;
let categories = categories::populate(&pool, row_count, &bar)?;
let survey_ids = surveys::populate(&pool, &usernames, &categories, row_count, &bar)?;
let question_ids = questions::populate(&pool, &survey_ids, row_count, &bar)?;
let choice_ids = choices::populate(&pool, &question_ids, row_count, &bar)?;
votes::populate(&pool, &usernames, &choice_ids, &bar)?;
bar.finish();
println!("\r\n 🐦 Birdseed has Finished Seeding! 🐦\r\n",);
Ok(())
}
fn populate_icecream(row_count: u32) -> Result<(), Box<dyn Error>> {
dotenv().ok();
let base_url = env::var("PSQL_URL")?;
env::set_var("DATABASE_URL", &format!("{}{}", base_url, "libellis"));
let pool = pg_pool::generate_pool();
println!("\r\n 🐦 Seeding All Tables 🐦\r\n",);
let bar = ProgressBar::new((row_count * 9) as u64);
bar.set_style(
ProgressStyle::default_bar()
.template("[{elapsed_precise}] {bar:40.cyan/blue} {msg}")
.progress_chars("##-"),
);
let usernames = users::populate(&pool, row_count, &bar)?;
{
let pool = pool.clone();
let conn = pool.get().unwrap();
categories::create(&conn, "food");
}
let survey_id;
{
let pool = pool.clone();
let conn = pool.get().unwrap();
let survey_title = "What is your favorite icecream?";
let cat = "food";
let survey = surveys::create(&conn, &usernames[0], &survey_title, cat);
survey_id = survey.id;
}
let question_id;
{
let pool = pool.clone();
let conn = pool.get().unwrap();
let q_title = "What is your favorite icecream?";
let q_type = "multiple".to_string();
let question = questions::create(&conn, survey_id, &q_type, &q_title);
question_id = question.id;
}
let choices = vec!["Strawberry", "Vanilla", "Chocolate"];
let choice_ids;
{
let pool = pool.clone();
let conn = pool.get().unwrap();
choice_ids = choices
.into_iter()
.map(|choice| {
let c_type = "text".to_string();
let choice_struct = choices::create(&conn, question_id, &c_type, choice);
choice_struct.id
})
.collect();
}
votes::populate_icecream(&pool, &usernames, &choice_ids, &bar)?;
bar.finish();
println!("\r\n 🐦 Birdseed has Finished Seeding! 🐦\r\n",);
Ok(())
}
fn migrate(database: &str) -> Result<(), Box<dyn Error>> {
dotenv().ok();
let base_url = env::var("PSQL_URL")?;
match database {
"all" | "a" => {
migrate_main(&base_url)?;
migrate_test(&base_url)?;
}
"main" | "m" => migrate_main(&base_url)?,
"test" | "t" => migrate_test(&base_url)?,
_ => {
return Err(io::Error::new(
InvalidInput,
"Invalid Database Type, choose 'main', 'test', or 'all'",
)
.into());
}
};
Ok(())
}
fn migrate_main(base_url: &String) -> Result<(), Box<dyn Error>> {
env::set_var("DATABASE_URL", &format!("{}{}", base_url, "libellis"));
let conn = establish_connection();
println!("\r\n 🐦 Running Migrations on Main DB 🐦\r\n");
embedded_migrations::run_with_output(&conn, &mut std::io::stdout())?;
Ok(())
}
fn migrate_test(base_url: &String) -> Result<(), Box<dyn Error>> {
env::set_var("DATABASE_URL", &format!("{}{}", base_url, "libellis_test"));
let conn = establish_connection();
println!("\r\n 🐦 Running Migrations on Test DB 🐦\r\n");
embedded_migrations::run_with_output(&conn, &mut std::io::stdout())?;
Ok(())
}
fn clear_all() -> Result<(), Box<dyn Error>> {
use self::schema::*;
dotenv().ok();
let base_url = env::var("PSQL_URL")?;
std::env::set_var("DATABASE_URL", &format!("{}{}", base_url, "libellis"));
let conn = establish_connection();
println!("\r\n 🐦 Clearing all Tables 🐦\r\n");
let bar = ProgressBar::new(7);
bar.set_style(
ProgressStyle::default_bar()
.template("[{elapsed_precise}] {bar:40.cyan/blue} {msg}")
.progress_chars("##-"),
);
diesel::delete(votes::table).execute(&conn)?;
bar.inc(1);
diesel::delete(fences::table).execute(&conn)?;
bar.inc(1);
diesel::delete(choices::table).execute(&conn)?;
bar.inc(1);
diesel::delete(questions::table).execute(&conn)?;
bar.inc(1);
diesel::delete(surveys::table).execute(&conn)?;
bar.inc(1);
diesel::delete(categories::table).execute(&conn)?;
bar.inc(1);
diesel::delete(users::table).execute(&conn)?;
bar.inc(1);
bar.finish();
println!("\r\n 🐦 All Tables Cleared! 🐦\r\n");
Ok(())
}
#[allow(unused_must_use)]
fn drop_all(conn: &PgConnection) {
let drop_statements = vec![
"DROP VIEW users_votes",
"DROP TABLE votes",
"DROP TABLE fences cascade",
"DROP EXTENSION postgis",
"DROP TABLE choices",
"DROP TABLE questions",
"DROP TABLE surveys",
"DROP TABLE categories",
"DROP TABLE users",
"DROP TABLE __diesel_schema_migrations",
];
let bar = ProgressBar::new(drop_statements.len() as u64);
bar.set_style(
ProgressStyle::default_bar()
.template("[{elapsed_precise}] {bar:40.cyan/blue} {msg}")
.progress_chars("##-"),
);
drop_statements.iter().for_each(|statement| {
conn.execute(statement);
bar.inc(1);
});
bar.finish();
}
fn rebuild(database: &str) -> Result<(), Box<dyn Error>> {
dotenv().ok();
let base_url = env::var("PSQL_URL")?;
match database {
"all" | "a" => {
rebuild_main(&base_url)?;
rebuild_test(&base_url)?;
}
"main" | "m" => rebuild_main(&base_url)?,
"test" | "t" => rebuild_test(&base_url)?,
_ => {
return Err(io::Error::new(
InvalidInput,
"Invalid Database Type, choose 'main', 'test', or 'all'",
)
.into());
}
};
Ok(())
}
fn rebuild_main(base_url: &str) -> Result<(), Box<dyn Error>> {
std::env::set_var("DATABASE_URL", &format!("{}{}", base_url, "libellis"));
println!("\r\n 🐦 Connecting to Main DB 🐦\r\n");
let conn = establish_connection();
println!("\r\n 🐦 Dropping all Tables 🐦\r\n");
drop_all(&conn);
println!("\r\n 🐦 Running Migrations 🐦\r\n");
embedded_migrations::run_with_output(&conn, &mut std::io::stdout())?;
println!("\r\n 🐦 Tables Successfully Rebuilt! 🐦\r\n");
Ok(())
}
fn rebuild_test(base_url: &str) -> Result<(), Box<dyn Error>> {
std::env::set_var("DATABASE_URL", &format!("{}{}", base_url, "libellis_test"));
println!("\r\n 🐦 Connecting to Test DB 🐦\r\n");
let conn = establish_connection();
println!("\r\n 🐦 Dropping all Tables 🐦\r\n");
drop_all(&conn);
println!("\r\n 🐦 Running Migrations 🐦\r\n");
embedded_migrations::run_with_output(&conn, &mut std::io::stdout())?;
println!("\r\n 🐦 Tables Successfully Rebuilt! 🐦\r\n");
Ok(())
}
fn establish_connection() -> PgConnection {
dotenv().ok();
let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set");
PgConnection::establish(&database_url).expect(&format!("Error connecting to {}", database_url))
}