Config
This commit is contained in:
parent
f55f2c4a0d
commit
9d6534c2a2
4
.env
4
.env
@ -1,7 +1,7 @@
|
||||
DATABASE_URL=postgres://postgres@localhost/bazzar
|
||||
PASS_SALT=18CHwV7eGFAea16z+qMKZg
|
||||
RUST_LOG=debug
|
||||
KEY_SECRET="NEPJs#8jjn8SK8GC7QEC^*P844UgsyEbQB8mRWXkT%3mPrwewZoc25MMby9H#R*w2KzaQgMkk#Pif$kxrLy*N5L!Ch%jxbWoa%gb"
|
||||
SESSION_SECRET="NEPJs#8jjn8SK8GC7QEC^*P844UgsyEbQB8mRWXkT%3mPrwewZoc25MMby9H#R*w2KzaQgMkk#Pif$kxrLy*N5L!Ch%jxbWoa%gb"
|
||||
JWT_SECRET="42^iFq&ZnQbUf!hwGWXd&CpyY6QQyJmkPU%esFCvne5&Ejcb3nJ4&GyHZp!MArZLf^9*5c6!!VgM$iZ8T%d#&bWTi&xbZk2S@4RN"
|
||||
PGDATESTYLE=
|
||||
|
||||
@ -12,3 +12,5 @@ SMTP_FROM=adrian.wozniak@ita-prog.pl
|
||||
PAYU_CLIENT_ID="145227"
|
||||
PAYU_CLIENT_SECRET="12f071174cb7eb79d4aac5bc2f07563f"
|
||||
PAYU_CLIENT_MERCHANT_ID=300746
|
||||
|
||||
WEB_HOST=https://bazzar.ita-prog.pl
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -1 +1,2 @@
|
||||
/target
|
||||
bazzar.toml
|
||||
|
@ -1,9 +1,12 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use actix::Addr;
|
||||
use parking_lot::Mutex;
|
||||
use pay_u::{MerchantPosId, OrderCreateRequest};
|
||||
|
||||
use crate::model::{Price, Quantity};
|
||||
use crate::database;
|
||||
use crate::database::Database;
|
||||
use crate::model::{AccountId, Price, ProductId, Quantity, QuantityUnit, ShoppingCartId};
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! pay_async_handler {
|
||||
@ -13,8 +16,9 @@ macro_rules! pay_async_handler {
|
||||
|
||||
fn handle(&mut self, msg: $msg, _ctx: &mut Self::Context) -> Self::Result {
|
||||
use actix::WrapFuture;
|
||||
let db = self.client.clone();
|
||||
Box::pin(async { $async(msg, db).await }.into_actor(self))
|
||||
let client = self.client.clone();
|
||||
let db = self.db.clone();
|
||||
Box::pin(async { $async(msg, client, db).await }.into_actor(self))
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -26,6 +30,8 @@ pub type PayUClient = Arc<Mutex<pay_u::Client>>;
|
||||
pub enum Error {
|
||||
#[error("{0}")]
|
||||
PayU(#[from] pay_u::Error),
|
||||
#[error("Failed to create order")]
|
||||
CreateOrder,
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
@ -33,6 +39,7 @@ pub type Result<T> = std::result::Result<T, Error>;
|
||||
#[derive(Clone)]
|
||||
pub struct PaymentManager {
|
||||
client: PayUClient,
|
||||
db: Addr<Database>,
|
||||
}
|
||||
|
||||
impl PaymentManager {
|
||||
@ -40,6 +47,7 @@ impl PaymentManager {
|
||||
client_id: ClientId,
|
||||
client_secret: ClientSecret,
|
||||
merchant_pos_id: MerchantPosId,
|
||||
db: Addr<Database>,
|
||||
) -> Result<Self>
|
||||
where
|
||||
ClientId: Into<pay_u::ClientId>,
|
||||
@ -50,6 +58,7 @@ impl PaymentManager {
|
||||
client.authorize().await?;
|
||||
Ok(Self {
|
||||
client: Arc::new(Mutex::new(client)),
|
||||
db,
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -85,8 +94,10 @@ impl From<Buyer> for pay_u::Buyer {
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Product {
|
||||
pub id: ProductId,
|
||||
pub name: String,
|
||||
pub unit_price: Price,
|
||||
pub quantity_unit: QuantityUnit,
|
||||
pub quantity: Quantity,
|
||||
}
|
||||
|
||||
@ -105,6 +116,8 @@ pub struct RequestPayment {
|
||||
pub description: String,
|
||||
pub buyer: Buyer,
|
||||
pub customer_ip: String,
|
||||
pub buyer_id: AccountId,
|
||||
pub shopping_cart_id: ShoppingCartId,
|
||||
}
|
||||
|
||||
pay_async_handler!(RequestPayment, request_payment, pay_u::OrderId);
|
||||
@ -112,8 +125,36 @@ pay_async_handler!(RequestPayment, request_payment, pay_u::OrderId);
|
||||
pub(crate) async fn request_payment(
|
||||
msg: RequestPayment,
|
||||
client: PayUClient,
|
||||
db: Addr<Database>,
|
||||
) -> Result<pay_u::OrderId> {
|
||||
let client = &mut *client.lock();
|
||||
let db_order = match db
|
||||
.send(database::CreateAccountOrder {
|
||||
buyer_id: msg.buyer_id,
|
||||
items: msg
|
||||
.products
|
||||
.iter()
|
||||
.map(|product| database::create_order::OrderItem {
|
||||
product_id: product.id,
|
||||
quantity: product.quantity,
|
||||
quantity_unit: product.quantity_unit,
|
||||
})
|
||||
.collect(),
|
||||
shopping_cart_id: msg.shopping_cart_id,
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(Ok(order)) => order,
|
||||
Ok(Err(e)) => {
|
||||
log::error!("{e}");
|
||||
return Err(Error::CreateOrder);
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("{e:?}");
|
||||
return Err(Error::CreateOrder);
|
||||
}
|
||||
};
|
||||
|
||||
let mut client = client.lock();
|
||||
let order = client
|
||||
.create_order(
|
||||
OrderCreateRequest::new(msg.buyer.into(), msg.customer_ip, msg.currency)
|
||||
|
272
api/src/config.rs
Normal file
272
api/src/config.rs
Normal file
@ -0,0 +1,272 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
trait Example: Sized {
|
||||
fn example() -> Self;
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Default)]
|
||||
pub struct PaymentConfig {
|
||||
payu_client_id: Option<pay_u::ClientId>,
|
||||
payu_client_secret: Option<pay_u::ClientSecret>,
|
||||
payu_client_merchant_id: Option<pay_u::MerchantPosId>,
|
||||
}
|
||||
|
||||
impl Example for PaymentConfig {
|
||||
fn example() -> Self {
|
||||
Self {
|
||||
payu_client_id: Some(pay_u::ClientId::new(
|
||||
"Create payu account and copy here client_id",
|
||||
)),
|
||||
payu_client_secret: Some(pay_u::ClientSecret::new(
|
||||
"Create payu account and copy here client_secret",
|
||||
)),
|
||||
/// "Create payu account and copy here merchant id"
|
||||
payu_client_merchant_id: Some(pay_u::MerchantPosId::from(0)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PaymentConfig {
|
||||
pub fn payu_client_id(&self) -> pay_u::ClientId {
|
||||
self.payu_client_id
|
||||
.as_ref()
|
||||
.cloned()
|
||||
.or_else(|| std::env::var("PAYU_CLIENT_ID").ok().map(pay_u::ClientId))
|
||||
.unwrap_or_else(|| {
|
||||
panic!("payment config payu_client_id nor PAYU_CLIENT_ID env was given")
|
||||
})
|
||||
}
|
||||
pub fn payu_client_secret(&self) -> pay_u::ClientSecret {
|
||||
self.payu_client_secret
|
||||
.as_ref()
|
||||
.cloned()
|
||||
.or_else(|| {
|
||||
std::env::var("PAYU_CLIENT_SECRET")
|
||||
.ok()
|
||||
.map(pay_u::ClientSecret)
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
panic!("payment config payu_client_secret nor PAYU_CLIENT_SECRET env was given")
|
||||
})
|
||||
}
|
||||
pub fn payu_client_merchant_id(&self) -> pay_u::MerchantPosId {
|
||||
self.payu_client_merchant_id
|
||||
.or_else(|| std::env::var("PAYU_CLIENT_MERCHANT_ID").ok().and_then(|s| s.parse::<i32>().ok()).map(pay_u::MerchantPosId))
|
||||
.unwrap_or_else(|| panic!("payment config payu_client_merchant_id nor PAYU_CLIENT_MERCHANT_ID env was given"))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Default)]
|
||||
pub struct WebConfig {
|
||||
/// Host name
|
||||
/// Example: https://foo.bar
|
||||
host: Option<String>,
|
||||
/// Encrypt password salt
|
||||
pass_salt: Option<String>,
|
||||
/// Used by redis to save admin session across actors
|
||||
session_secret: Option<String>,
|
||||
/// Encrypt JWT
|
||||
jwt_secret: Option<String>,
|
||||
bind: Option<String>,
|
||||
port: Option<u16>,
|
||||
}
|
||||
|
||||
impl Example for WebConfig {
|
||||
fn example() -> Self {
|
||||
Self {
|
||||
host: Some(String::from("https://your.comain.com")),
|
||||
pass_salt: Some(String::from("Generate it with bazzar generate-hash")),
|
||||
session_secret: Some(String::from("100 characters long random string")),
|
||||
jwt_secret: Some(String::from("100 characters long random string")),
|
||||
bind: Some(String::from("0.0.0.0")),
|
||||
port: Some(8080),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl WebConfig {
|
||||
pub fn host(&self) -> String {
|
||||
self.host
|
||||
.as_ref()
|
||||
.cloned()
|
||||
.or_else(|| std::env::var("WEB_HOST").ok())
|
||||
.unwrap_or_else(|| panic!("web host config nor WEB_HOST env was not given"))
|
||||
}
|
||||
pub fn pass_salt(&self) -> String {
|
||||
self.pass_salt
|
||||
.as_ref()
|
||||
.cloned()
|
||||
.or_else(|| std::env::var("PASS_SALT").ok())
|
||||
.unwrap_or_else(|| panic!("Web config pass_salt nor PASS_SALT env was given"))
|
||||
}
|
||||
pub fn session_secret(&self) -> String {
|
||||
self.session_secret
|
||||
.as_ref()
|
||||
.cloned()
|
||||
.or_else(|| std::env::var("SESSION_SECRET").ok())
|
||||
.unwrap_or_else(|| panic!("Web config session_secret nor SESSION_SECRET env was given"))
|
||||
}
|
||||
pub fn jwt_secret(&self) -> String {
|
||||
self.jwt_secret
|
||||
.as_ref()
|
||||
.cloned()
|
||||
.or_else(|| std::env::var("JWT_SECRET").ok())
|
||||
.unwrap_or_else(|| panic!("Web config jwt_secret nor JWT_SECRET env was given"))
|
||||
}
|
||||
pub fn bind(&self) -> Option<String> {
|
||||
self.bind
|
||||
.as_ref()
|
||||
.cloned()
|
||||
.or_else(|| std::env::var("BAZZAR_BIND").ok())
|
||||
}
|
||||
pub fn port(&self) -> Option<u16> {
|
||||
self.port.as_ref().copied().or_else(|| {
|
||||
std::env::var("BAZZAR_PORT")
|
||||
.ok()
|
||||
.and_then(|s| s.parse::<u16>().ok())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Default)]
|
||||
pub struct MailConfig {
|
||||
sendgrid_secret: Option<String>,
|
||||
sendgrid_api_key: Option<String>,
|
||||
smtp_from: Option<String>,
|
||||
}
|
||||
|
||||
impl Example for MailConfig {
|
||||
fn example() -> Self {
|
||||
Self {
|
||||
sendgrid_secret: Some(String::from(
|
||||
"Create sendgrid account and copy credentials here",
|
||||
)),
|
||||
sendgrid_api_key: Some(String::from(
|
||||
"Create sendgrid account and copy credentials here",
|
||||
)),
|
||||
smtp_from: Some(String::from(
|
||||
"Valid sendgrid authorized email address. Example: contact@example.com",
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MailConfig {
|
||||
pub fn sendgrid_secret(&self) -> String {
|
||||
self.sendgrid_secret
|
||||
.as_ref()
|
||||
.cloned()
|
||||
.or_else(|| std::env::var("SENDGRID_SECRET").ok())
|
||||
.unwrap_or_else(|| {
|
||||
panic!("Mail sendgrid_secret config nor SENDGRID_SECRET env was given")
|
||||
})
|
||||
}
|
||||
pub fn sendgrid_api_key(&self) -> String {
|
||||
self.sendgrid_api_key
|
||||
.as_ref()
|
||||
.cloned()
|
||||
.or_else(|| std::env::var("SENDGRID_API_KEY").ok())
|
||||
.unwrap_or_else(|| {
|
||||
panic!("Mail sendgrid_api_key config nor SENDGRID_API_KEY env was given")
|
||||
})
|
||||
}
|
||||
pub fn smtp_from(&self) -> String {
|
||||
self.smtp_from
|
||||
.as_ref()
|
||||
.cloned()
|
||||
.or_else(|| std::env::var("SMTP_FROM").ok())
|
||||
.unwrap_or_else(|| panic!("Mail smtp_from config nor SMTP_FROM env was given"))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Default)]
|
||||
pub struct DatabaseConfig {
|
||||
url: Option<String>,
|
||||
}
|
||||
|
||||
impl Example for DatabaseConfig {
|
||||
fn example() -> Self {
|
||||
Self {
|
||||
url: Some(String::from("postgres://postgres@localhost/bazzar")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DatabaseConfig {
|
||||
pub fn url(&self) -> String {
|
||||
self.url
|
||||
.as_ref()
|
||||
.cloned()
|
||||
.or_else(|| std::env::var("DATABASE_URL").ok())
|
||||
.unwrap_or_else(|| panic!("Database url nor DATABASE_URL env was given"))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct AppConfig {
|
||||
payment: PaymentConfig,
|
||||
web: WebConfig,
|
||||
mail: MailConfig,
|
||||
database: DatabaseConfig,
|
||||
#[serde(skip)]
|
||||
config_path: String,
|
||||
}
|
||||
|
||||
impl Example for AppConfig {
|
||||
fn example() -> Self {
|
||||
Self {
|
||||
payment: PaymentConfig::example(),
|
||||
web: WebConfig::example(),
|
||||
mail: MailConfig::example(),
|
||||
database: DatabaseConfig::example(),
|
||||
config_path: "".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AppConfig {
|
||||
pub fn payment(&self) -> &PaymentConfig {
|
||||
&self.payment
|
||||
}
|
||||
pub fn web(&self) -> &WebConfig {
|
||||
&self.web
|
||||
}
|
||||
pub fn mail(&self) -> &MailConfig {
|
||||
&self.mail
|
||||
}
|
||||
pub fn database(&self) -> &DatabaseConfig {
|
||||
&self.database
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for AppConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
payment: Default::default(),
|
||||
web: WebConfig::default(),
|
||||
mail: Default::default(),
|
||||
database: DatabaseConfig::default(),
|
||||
config_path: "".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load(config_path: &str) -> AppConfig {
|
||||
match std::fs::read_to_string(config_path) {
|
||||
Ok(c) => toml::from_str(&c).unwrap(),
|
||||
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
|
||||
let config = AppConfig::example();
|
||||
std::fs::write(config_path, toml::to_string_pretty(&config).unwrap()).unwrap();
|
||||
config
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("{e:?}");
|
||||
panic!("Config file was not found at path {config_path:?}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn save(config_path: &str, config: &mut AppConfig) {
|
||||
config.config_path = String::from(config_path);
|
||||
std::fs::write(config_path, toml::to_string_pretty(&config).unwrap()).unwrap();
|
||||
}
|
@ -20,6 +20,7 @@ use crate::logic::encrypt_password;
|
||||
use crate::model::{Email, Login, PassHash, Password, Role};
|
||||
|
||||
pub mod actors;
|
||||
pub mod config;
|
||||
pub mod logic;
|
||||
pub mod model;
|
||||
pub mod routes;
|
||||
@ -174,12 +175,14 @@ impl Config {
|
||||
|
||||
async fn server(opts: ServerOpts) -> Result<()> {
|
||||
let secret_key = {
|
||||
let key_secret = std::env::var("KEY_SECRET")
|
||||
let key_secret = std::env::var("SESSION_SECRET")
|
||||
.expect("session requires secret key with 64 or more characters");
|
||||
Key::from(key_secret.as_bytes())
|
||||
};
|
||||
let redis_connection_string = "127.0.0.1:6379";
|
||||
|
||||
let app_config = crate::config::load("./bazzar.toml");
|
||||
|
||||
let config = Arc::new(Config::load());
|
||||
let db = database::Database::build(&opts.db_url()).await?.start();
|
||||
let token_manager = token_manager::TokenManager::new(db.clone()).start();
|
||||
@ -193,7 +196,7 @@ async fn server(opts: ServerOpts) -> Result<()> {
|
||||
.parse::<i32>()
|
||||
.map(MerchantPosId::from)
|
||||
.expect("Variable PAYU_CLIENT_MERCHANT_ID must be number");
|
||||
payment_manager::PaymentManager::build(client_id, client_secret, merchant_id)
|
||||
payment_manager::PaymentManager::build(client_id, client_secret, merchant_id, db.clone())
|
||||
.await
|
||||
.expect("Failed to start payment manager")
|
||||
.start()
|
||||
@ -216,7 +219,10 @@ async fn server(opts: ServerOpts) -> Result<()> {
|
||||
.configure(routes::configure)
|
||||
// .default_service(web::to(HttpResponse::Ok))
|
||||
})
|
||||
.bind((opts.bind, opts.port))
|
||||
.bind((
|
||||
app_config.web().bind().unwrap_or(opts.bind),
|
||||
app_config.web().port().unwrap_or(opts.port),
|
||||
))
|
||||
.map_err(Error::Boot)?
|
||||
.run()
|
||||
.await
|
||||
|
@ -131,12 +131,12 @@ impl Default for Audience {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(sqlx::Type, Serialize, Deserialize, Debug, Deref, From)]
|
||||
#[derive(sqlx::Type, Serialize, Deserialize, Default, Debug, Copy, Clone, Deref, From)]
|
||||
#[sqlx(transparent)]
|
||||
#[serde(transparent)]
|
||||
pub struct Price(NonNegative);
|
||||
|
||||
#[derive(sqlx::Type, Serialize, Deserialize, Default, Debug, Deref, From)]
|
||||
#[derive(sqlx::Type, Serialize, Deserialize, Default, Debug, Copy, Clone, Deref, From)]
|
||||
#[sqlx(transparent)]
|
||||
#[serde(transparent)]
|
||||
pub struct Quantity(NonNegative);
|
||||
@ -200,7 +200,7 @@ impl<'de> serde::Deserialize<'de> for Email {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(sqlx::Type, Serialize, Default, Debug, Deref, Display)]
|
||||
#[derive(sqlx::Type, Serialize, Default, Debug, Copy, Clone, Deref, Display)]
|
||||
#[sqlx(transparent)]
|
||||
#[serde(transparent)]
|
||||
pub struct NonNegative(i32);
|
||||
|
@ -1,5 +1,2 @@
|
||||
ALTER TABLE accounts
|
||||
ADD COLUMN customer_id uuid not null default gen_random_uuid();
|
||||
|
||||
ALTER TABLE account_orders
|
||||
ADD COLUMN order_id varchar unique;
|
||||
|
2
db/migrate/202204271359_add_order_ext_id.sql
Normal file
2
db/migrate/202204271359_add_order_ext_id.sql
Normal file
@ -0,0 +1,2 @@
|
||||
ALTER TABLE account_orders
|
||||
ADD COLUMN order_ext_id uuid not null default uuid_generate_v4();
|
Loading…
Reference in New Issue
Block a user