bazzar/crates/api/src/main.rs

239 lines
7.7 KiB
Rust
Raw Normal View History

2022-05-01 18:25:27 +02:00
#![feature(drain_filter)]
2022-04-19 16:49:30 +02:00
use std::io::Write;
2022-05-13 15:24:11 +02:00
use std::str::FromStr;
2022-04-14 21:40:26 +02:00
use actix::Actor;
2022-04-18 22:07:52 +02:00
use actix_session::storage::RedisActorSessionStore;
use actix_session::SessionMiddleware;
2022-04-14 21:40:26 +02:00
use actix_web::middleware::Logger;
use actix_web::web::Data;
use actix_web::{App, HttpServer};
2022-05-06 11:47:18 +02:00
use config::UpdateConfig;
use email_manager::TestMail;
2022-04-20 14:30:59 +02:00
use jemallocator::Jemalloc;
2022-05-08 09:47:05 +02:00
use model::{Email, Encrypt, Login, PassHash, Password, Role};
use opts::{
2022-11-04 21:26:30 +01:00
Command, CreateAccountCmd, CreateAccountOpts, GenerateHashOpts, Opts, ServerOpts,
TestMailerOpts,
};
2022-04-14 21:40:26 +02:00
use validator::{validate_email, validate_length};
2022-05-13 15:24:11 +02:00
use crate::opts::ReIndexOpts;
mod opts;
2022-04-14 21:40:26 +02:00
pub mod routes;
2022-04-20 14:30:59 +02:00
#[global_allocator]
static GLOBAL: Jemalloc = Jemalloc;
2022-04-14 21:40:26 +02:00
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("Failed to boot. {0:?}")]
Boot(std::io::Error),
#[error("Unable to read password file. {0:?}")]
PassFile(std::io::Error),
#[error("Unable to read password from STDIN. {0:?}")]
ReadPass(std::io::Error),
#[error("{0}")]
2022-05-06 11:47:18 +02:00
Database(#[from] database_manager::Error),
2022-04-14 21:40:26 +02:00
}
pub type Result<T> = std::result::Result<T, Error>;
async fn server(opts: ServerOpts) -> Result<()> {
let redis_connection_string = "127.0.0.1:6379";
2022-05-01 17:27:56 +02:00
let app_config = config::default_load(&opts);
2022-04-27 16:25:57 +02:00
2022-05-06 11:47:18 +02:00
let db = database_manager::Database::build(app_config.clone())
2022-05-22 14:19:11 +02:00
.await
2022-05-06 11:47:18 +02:00
.start();
let token_manager = token_manager::TokenManager::new(app_config.clone(), db.clone()).start();
let order_manager = order_manager::OrderManager::new(app_config.clone(), db.clone()).start();
let payment_manager = payment_manager::PaymentManager::build(app_config.clone(), db.clone())
.await
.start();
2022-05-13 15:24:11 +02:00
let search_manager = search_manager::SearchManager::new(app_config.clone()).start();
2022-05-06 16:02:38 +02:00
let fs_manager = fs_manager::FsManager::build(app_config.clone())
.await
.expect("Failed to initialize file system storage");
2022-11-04 21:26:30 +01:00
let cart_manager = channels::carts::rpc::create_client(app_config.clone()).await;
let account_manager = channels::accounts::rpc::create_client(app_config.clone()).await;
2022-05-04 07:26:29 +02:00
let addr = {
let l = app_config.lock();
let w = l.web();
(w.bind().unwrap_or(opts.bind), w.port().unwrap_or(opts.port))
};
println!("Listen at {:?}", addr);
2022-04-14 21:40:26 +02:00
HttpServer::new(move || {
let config = app_config.clone();
2022-04-14 21:40:26 +02:00
App::new()
.wrap(Logger::default())
.wrap(SessionMiddleware::new(
RedisActorSessionStore::new(redis_connection_string),
2022-05-04 07:26:29 +02:00
{
let l = config.lock();
l.web().session_secret()
},
2022-04-14 21:40:26 +02:00
))
2022-05-04 07:26:29 +02:00
.wrap(actix_web::middleware::Compress::default())
.wrap(actix_web::middleware::NormalizePath::trim())
.app_data(Data::new(config))
2022-04-14 21:40:26 +02:00
.app_data(Data::new(db.clone()))
2022-04-18 22:07:52 +02:00
.app_data(Data::new(token_manager.clone()))
2022-04-20 16:09:09 +02:00
.app_data(Data::new(order_manager.clone()))
2022-04-25 08:19:20 +02:00
.app_data(Data::new(payment_manager.clone()))
2022-05-06 07:50:33 +02:00
.app_data(Data::new(search_manager.clone()))
2022-05-06 16:02:38 +02:00
.app_data(Data::new(fs_manager.clone()))
2022-05-19 14:03:18 +02:00
.app_data(Data::new(cart_manager.clone()))
2022-05-20 16:08:49 +02:00
.app_data(Data::new(account_manager.clone()))
2022-04-14 21:40:26 +02:00
.configure(routes::configure)
2022-05-06 11:47:18 +02:00
.service({
let l = app_config.lock();
actix_files::Files::new(&l.files().public_path(), l.files().local_path())
2022-05-09 16:17:27 +02:00
.use_etag(true)
2022-05-06 11:47:18 +02:00
})
.default_service(actix_web::web::to(actix_web::HttpResponse::Ok))
2022-04-14 21:40:26 +02:00
})
.bind(addr)
2022-04-14 21:40:26 +02:00
.map_err(Error::Boot)?
.run()
.await
.map_err(Error::Boot)
}
async fn generate_hash(_opts: GenerateHashOpts) -> Result<()> {
2022-05-08 09:47:05 +02:00
model::print_hash();
2022-04-14 21:40:26 +02:00
Ok(())
}
async fn create_account(opts: CreateAccountOpts) -> Result<()> {
let (role, opts) = match opts.cmd.expect("Account type is mandatory") {
CreateAccountCmd::Admin(opts) => (Role::Admin, opts),
CreateAccountCmd::User(opts) => (Role::User, opts),
};
if !validate_email(&opts.email) {
panic!("Invalid email address");
}
if !validate_length(&opts.login, Some(4), Some(100), None) {
panic!("Login must have at least 4 characters and no more than 100");
}
2022-05-01 17:27:56 +02:00
let config = config::default_load(&opts);
2022-05-06 11:47:18 +02:00
let db = database_manager::Database::build(config.clone())
2022-05-22 14:19:11 +02:00
.await
2022-05-06 11:47:18 +02:00
.start();
2022-04-14 21:40:26 +02:00
let pass = match opts.pass_file {
Some(path) => std::fs::read_to_string(path).map_err(Error::PassFile)?,
None => {
let mut s = String::with_capacity(100);
2022-04-18 22:07:52 +02:00
{
2022-04-19 16:49:30 +02:00
let mut std_out = std::io::stdout();
let std_in = std::io::stdin();
2022-04-18 22:07:52 +02:00
std_out
.write_all(b"PASS > ")
.expect("Failed to write to stdout");
std_out.flush().expect("Failed to write to stdout");
std_in.read_line(&mut s).map_err(Error::ReadPass)?;
}
if let Some(pos) = s.chars().position(|c| c == '\n') {
s.remove(pos);
}
2022-04-14 21:40:26 +02:00
s
}
};
2022-04-18 22:07:52 +02:00
if pass.trim().is_empty() {
panic!("Password cannot be empty!");
}
2022-05-08 09:47:05 +02:00
let hash = Password::from(pass)
.encrypt(&config.lock().web().pass_salt())
.unwrap();
2022-04-14 21:40:26 +02:00
2022-05-06 11:47:18 +02:00
db.send(database_manager::CreateAccount {
2022-05-13 15:24:11 +02:00
email: Email::from_str(&opts.email).unwrap(),
login: Login::new(opts.login),
2022-04-19 16:49:30 +02:00
pass_hash: PassHash::from(hash),
2022-04-14 21:40:26 +02:00
role,
})
.await
.unwrap()
.unwrap();
Ok(())
}
2022-04-21 16:06:34 +02:00
async fn test_mailer(opts: TestMailerOpts) -> Result<()> {
2022-05-01 17:27:56 +02:00
let config = config::default_load(&opts);
opts.update_config(&mut *config.lock());
let manager = email_manager::EmailManager::build(config)
2022-04-21 16:06:34 +02:00
.expect("Invalid email manager config")
.start();
2022-05-09 16:17:27 +02:00
manager
2022-04-21 16:06:34 +02:00
.send(TestMail {
receiver: opts.receiver.expect("e-mail address is required"),
})
.await
.expect("Failed to execute actor")
2022-05-09 16:17:27 +02:00
.expect("Failed to send email");
println!("Success!");
2022-04-21 16:06:34 +02:00
Ok(())
}
2022-05-13 15:24:11 +02:00
async fn reindex(opts: ReIndexOpts) -> Result<()> {
let config = config::default_load(&opts);
opts.update_config(&mut *config.lock());
let db = database_manager::Database::build(config.clone())
2022-05-22 14:19:11 +02:00
.await
2022-05-13 15:24:11 +02:00
.start();
let search = search_manager::SearchManager::new(config).start();
let products: Vec<model::Product> = db
.send(database_manager::AllProducts)
.await
.unwrap()
.unwrap();
for product in products {
search
.send(search_manager::CreateIndex {
key: product.id.to_string(),
value: vec![
product.long_description.into_inner(),
product.short_description.into_inner(),
product.name.into_inner(),
]
.join(" "),
collection: "products".into(),
lang: opts.lang.clone(),
})
.await
.unwrap()
.unwrap();
}
println!("Success!");
Ok(())
}
2022-04-14 21:40:26 +02:00
#[actix_web::main]
async fn main() -> Result<()> {
2022-05-08 09:47:05 +02:00
human_panic::setup_panic!();
2022-04-14 21:40:26 +02:00
dotenv::dotenv().ok();
tracing_subscriber::fmt::init();
2022-04-14 21:40:26 +02:00
let opts: Opts = gumdrop::Options::parse_args_default_or_exit();
match opts.cmd.unwrap_or_default() {
Command::Server(opts) => server(opts).await,
Command::GenerateHash(opts) => generate_hash(opts).await,
Command::CreateAccount(opts) => create_account(opts).await,
2022-04-21 16:06:34 +02:00
Command::TestMailer(opts) => test_mailer(opts).await,
2022-05-06 11:47:18 +02:00
Command::ConfigInfo(_) => {
config::config_info().await.unwrap();
Ok(())
}
2022-05-13 15:24:11 +02:00
Command::ReIndex(opts) => reindex(opts).await,
2022-04-14 21:40:26 +02:00
}
2022-04-14 08:07:59 +02:00
}