Merge branch 'prices'
This commit is contained in:
@ -1,10 +1,13 @@
|
||||
use crate::exchange::Currency;
|
||||
use crate::provisioner::PricingEngine;
|
||||
use crate::exchange::{alt_prices, Currency, CurrencyAmount, ExchangeRateService};
|
||||
use crate::provisioner::{PricingData, PricingEngine};
|
||||
use crate::status::VmState;
|
||||
use anyhow::{bail, Result};
|
||||
use anyhow::{anyhow, bail, Context, Result};
|
||||
use chrono::{DateTime, Utc};
|
||||
use ipnetwork::IpNetwork;
|
||||
use lnvps_db::{LNVpsDb, Vm, VmCostPlan, VmCustomTemplate, VmHost, VmHostRegion, VmTemplate};
|
||||
use lnvps_db::{
|
||||
LNVpsDb, Vm, VmCostPlan, VmCustomPricing, VmCustomPricingDisk, VmCustomTemplate, VmHost,
|
||||
VmHostRegion, VmTemplate,
|
||||
};
|
||||
use nostr::util::hex;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@ -136,6 +139,22 @@ pub struct ApiTemplatesResponse {
|
||||
pub custom_template: Option<Vec<ApiCustomTemplateParams>>,
|
||||
}
|
||||
|
||||
impl ApiTemplatesResponse {
|
||||
pub async fn expand_pricing(&mut self, rates: &Arc<dyn ExchangeRateService>) -> Result<()> {
|
||||
let rates = rates.list_rates().await?;
|
||||
|
||||
for mut template in &mut self.templates {
|
||||
let list_price = CurrencyAmount(template.cost_plan.currency, template.cost_plan.amount);
|
||||
for alt_price in alt_prices(&rates, list_price) {
|
||||
template.cost_plan.other_price.push(ApiPrice {
|
||||
currency: alt_price.0,
|
||||
amount: alt_price.1,
|
||||
});
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
#[derive(Serialize, Deserialize, JsonSchema)]
|
||||
pub struct ApiCustomTemplateParams {
|
||||
pub id: u64,
|
||||
@ -150,6 +169,40 @@ pub struct ApiCustomTemplateParams {
|
||||
pub disks: Vec<ApiCustomTemplateDiskParam>,
|
||||
}
|
||||
|
||||
impl ApiCustomTemplateParams {
|
||||
pub fn from(
|
||||
pricing: &VmCustomPricing,
|
||||
disks: &Vec<VmCustomPricingDisk>,
|
||||
region: &VmHostRegion,
|
||||
max_cpu: u16,
|
||||
max_memory: u64,
|
||||
max_disk: u64,
|
||||
) -> Result<Self> {
|
||||
const GB: u64 = 1024 * 1024 * 1024;
|
||||
Ok(ApiCustomTemplateParams {
|
||||
id: pricing.id,
|
||||
name: pricing.name.clone(),
|
||||
region: ApiVmHostRegion {
|
||||
id: region.id,
|
||||
name: region.name.clone(),
|
||||
},
|
||||
max_cpu,
|
||||
min_cpu: 1,
|
||||
min_memory: GB,
|
||||
max_memory,
|
||||
min_disk: GB * 5,
|
||||
max_disk,
|
||||
disks: disks
|
||||
.iter()
|
||||
.filter(|d| d.pricing_id == pricing.id)
|
||||
.map(|d| ApiCustomTemplateDiskParam {
|
||||
disk_type: d.kind.into(),
|
||||
disk_interface: d.interface.into(),
|
||||
})
|
||||
.collect(),
|
||||
})
|
||||
}
|
||||
}
|
||||
#[derive(Clone, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct ApiCustomTemplateDiskParam {
|
||||
pub disk_type: DiskType,
|
||||
@ -189,12 +242,21 @@ impl From<ApiCustomVmRequest> for VmCustomTemplate {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, JsonSchema)]
|
||||
pub struct ApiCustomPrice {
|
||||
pub currency: String,
|
||||
#[derive(Copy, Clone, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct ApiPrice {
|
||||
pub currency: Currency,
|
||||
pub amount: f32,
|
||||
}
|
||||
|
||||
impl From<CurrencyAmount> for ApiPrice {
|
||||
fn from(value: CurrencyAmount) -> Self {
|
||||
Self {
|
||||
currency: value.0,
|
||||
amount: value.1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, JsonSchema)]
|
||||
pub struct ApiVmTemplate {
|
||||
pub id: u64,
|
||||
@ -216,7 +278,7 @@ impl ApiVmTemplate {
|
||||
let template = db.get_vm_template(template_id).await?;
|
||||
let cost_plan = db.get_cost_plan(template.cost_plan_id).await?;
|
||||
let region = db.get_host_region(template.region_id).await?;
|
||||
Ok(Self::from_standard_data(&template, &cost_plan, ®ion))
|
||||
Self::from_standard_data(&template, &cost_plan, ®ion)
|
||||
}
|
||||
|
||||
pub async fn from_custom(db: &Arc<dyn LNVpsDb>, vm_id: u64, template_id: u64) -> Result<Self> {
|
||||
@ -239,6 +301,7 @@ impl ApiVmTemplate {
|
||||
name: pricing.name,
|
||||
amount: price.total(),
|
||||
currency: price.currency,
|
||||
other_price: vec![], // filled externally
|
||||
interval_amount: 1,
|
||||
interval_type: ApiVmCostPlanIntervalType::Month,
|
||||
},
|
||||
@ -263,8 +326,8 @@ impl ApiVmTemplate {
|
||||
template: &VmTemplate,
|
||||
cost_plan: &VmCostPlan,
|
||||
region: &VmHostRegion,
|
||||
) -> Self {
|
||||
Self {
|
||||
) -> Result<Self> {
|
||||
Ok(Self {
|
||||
id: template.id,
|
||||
name: template.name.clone(),
|
||||
created: template.created,
|
||||
@ -278,7 +341,9 @@ impl ApiVmTemplate {
|
||||
id: cost_plan.id,
|
||||
name: cost_plan.name.clone(),
|
||||
amount: cost_plan.amount,
|
||||
currency: cost_plan.currency.clone(),
|
||||
currency: Currency::from_str(&cost_plan.currency)
|
||||
.map_err(|_| anyhow!("Invalid currency: {}", &cost_plan.currency))?,
|
||||
other_price: vec![], //filled externally
|
||||
interval_amount: cost_plan.interval_amount,
|
||||
interval_type: cost_plan.interval_type.clone().into(),
|
||||
},
|
||||
@ -286,7 +351,7 @@ impl ApiVmTemplate {
|
||||
id: region.id,
|
||||
name: region.name.clone(),
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
#[derive(Serialize, Deserialize, JsonSchema)]
|
||||
@ -311,8 +376,9 @@ impl From<lnvps_db::VmCostPlanIntervalType> for ApiVmCostPlanIntervalType {
|
||||
pub struct ApiVmCostPlan {
|
||||
pub id: u64,
|
||||
pub name: String,
|
||||
pub currency: Currency,
|
||||
pub amount: f32,
|
||||
pub currency: String,
|
||||
pub other_price: Vec<ApiPrice>,
|
||||
pub interval_amount: u64,
|
||||
pub interval_type: ApiVmCostPlanIntervalType,
|
||||
}
|
||||
|
@ -1,9 +1,10 @@
|
||||
use crate::api::model::{
|
||||
AccountPatchRequest, ApiCustomPrice, ApiCustomTemplateDiskParam, ApiCustomTemplateParams,
|
||||
ApiCustomVmOrder, ApiCustomVmRequest, ApiTemplatesResponse, ApiUserSshKey, ApiVmHostRegion,
|
||||
AccountPatchRequest, ApiCustomTemplateDiskParam, ApiCustomTemplateParams, ApiCustomVmOrder,
|
||||
ApiCustomVmRequest, ApiPrice, ApiTemplatesResponse, ApiUserSshKey, ApiVmHostRegion,
|
||||
ApiVmIpAssignment, ApiVmOsImage, ApiVmPayment, ApiVmStatus, ApiVmTemplate, CreateSshKey,
|
||||
CreateVmRequest, VMPatchRequest,
|
||||
};
|
||||
use crate::exchange::ExchangeRateService;
|
||||
use crate::host::{get_host_client, FullVmInfo, TimeSeries, TimeSeriesData};
|
||||
use crate::nip98::Nip98Auth;
|
||||
use crate::provisioner::{HostCapacityService, LNVpsProvisioner, PricingEngine};
|
||||
@ -266,7 +267,10 @@ async fn v1_list_vm_images(db: &State<Arc<dyn LNVpsDb>>) -> ApiResult<Vec<ApiVmO
|
||||
/// List available VM templates (Offers)
|
||||
#[openapi(tag = "VM")]
|
||||
#[get("/api/v1/vm/templates")]
|
||||
async fn v1_list_vm_templates(db: &State<Arc<dyn LNVpsDb>>) -> ApiResult<ApiTemplatesResponse> {
|
||||
async fn v1_list_vm_templates(
|
||||
db: &State<Arc<dyn LNVpsDb>>,
|
||||
rates: &State<Arc<dyn ExchangeRateService>>,
|
||||
) -> ApiResult<ApiTemplatesResponse> {
|
||||
let hc = HostCapacityService::new((*db).clone());
|
||||
let templates = hc.list_available_vm_templates().await?;
|
||||
|
||||
@ -301,7 +305,7 @@ async fn v1_list_vm_templates(db: &State<Arc<dyn LNVpsDb>>) -> ApiResult<ApiTemp
|
||||
.filter_map(|i| {
|
||||
let cp = cost_plans.get(&i.cost_plan_id)?;
|
||||
let hr = regions.get(&i.region_id)?;
|
||||
Some(ApiVmTemplate::from_standard_data(i, cp, hr))
|
||||
ApiVmTemplate::from_standard_data(i, cp, hr).ok()
|
||||
})
|
||||
.collect();
|
||||
let custom_templates: Vec<VmCustomPricing> =
|
||||
@ -322,49 +326,42 @@ async fn v1_list_vm_templates(db: &State<Arc<dyn LNVpsDb>>) -> ApiResult<ApiTemp
|
||||
.flatten()
|
||||
.collect();
|
||||
|
||||
ApiData::ok(ApiTemplatesResponse {
|
||||
let mut rsp = ApiTemplatesResponse {
|
||||
templates: ret,
|
||||
custom_template: if custom_templates.is_empty() {
|
||||
None
|
||||
} else {
|
||||
const GB: u64 = 1024 * 1024 * 1024;
|
||||
let max_cpu = templates.iter().map(|t| t.cpu).max().unwrap_or(8);
|
||||
let max_memory = templates.iter().map(|t| t.memory).max().unwrap_or(GB * 2);
|
||||
let max_disk = templates
|
||||
.iter()
|
||||
.map(|t| t.disk_size)
|
||||
.max()
|
||||
.unwrap_or(GB * 5);
|
||||
Some(
|
||||
custom_templates
|
||||
.into_iter()
|
||||
.map(|t| ApiCustomTemplateParams {
|
||||
id: t.id,
|
||||
name: t.name,
|
||||
region: regions
|
||||
.get(&t.region_id)
|
||||
.map(|r| ApiVmHostRegion {
|
||||
id: r.id,
|
||||
name: r.name.clone(),
|
||||
})
|
||||
.context("Region information missing in custom template")
|
||||
.unwrap(),
|
||||
max_cpu: templates.iter().map(|t| t.cpu).max().unwrap_or(8),
|
||||
min_cpu: 1,
|
||||
min_memory: GB,
|
||||
max_memory: templates.iter().map(|t| t.memory).max().unwrap_or(GB * 2),
|
||||
min_disk: GB * 5,
|
||||
max_disk: templates
|
||||
.iter()
|
||||
.map(|t| t.disk_size)
|
||||
.max()
|
||||
.unwrap_or(GB * 5),
|
||||
disks: custom_template_disks
|
||||
.iter()
|
||||
.filter(|d| d.pricing_id == t.id)
|
||||
.map(|d| ApiCustomTemplateDiskParam {
|
||||
disk_type: d.kind.into(),
|
||||
disk_interface: d.interface.into(),
|
||||
})
|
||||
.collect(),
|
||||
.filter_map(|t| {
|
||||
let region = regions.get(&t.region_id)?;
|
||||
Some(
|
||||
ApiCustomTemplateParams::from(
|
||||
&t,
|
||||
&custom_template_disks,
|
||||
region,
|
||||
max_cpu,
|
||||
max_memory,
|
||||
max_disk,
|
||||
)
|
||||
.ok()?,
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
},
|
||||
})
|
||||
};
|
||||
rsp.expand_pricing(rates).await?;
|
||||
ApiData::ok(rsp)
|
||||
}
|
||||
|
||||
/// Get a price for a custom order
|
||||
@ -373,12 +370,12 @@ async fn v1_list_vm_templates(db: &State<Arc<dyn LNVpsDb>>) -> ApiResult<ApiTemp
|
||||
async fn v1_custom_template_calc(
|
||||
db: &State<Arc<dyn LNVpsDb>>,
|
||||
req: Json<ApiCustomVmRequest>,
|
||||
) -> ApiResult<ApiCustomPrice> {
|
||||
) -> ApiResult<ApiPrice> {
|
||||
// create a fake template from the request to generate the price
|
||||
let template: VmCustomTemplate = req.0.into();
|
||||
|
||||
let price = PricingEngine::get_custom_vm_cost_amount(db, 0, &template).await?;
|
||||
ApiData::ok(ApiCustomPrice {
|
||||
ApiData::ok(ApiPrice {
|
||||
currency: price.currency.clone(),
|
||||
amount: price.total(),
|
||||
})
|
||||
|
@ -1,15 +1,16 @@
|
||||
use anyhow::{Error, Result};
|
||||
use anyhow::{anyhow, ensure, Context, Error, Result};
|
||||
use lnvps_db::async_trait;
|
||||
use log::info;
|
||||
use rocket::serde::Deserialize;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash, Serialize, Deserialize, Clone)]
|
||||
#[derive(Debug, PartialEq, Eq, Hash, Serialize, Deserialize, Clone, Copy, JsonSchema)]
|
||||
pub enum Currency {
|
||||
EUR,
|
||||
BTC,
|
||||
@ -39,12 +40,12 @@ impl FromStr for Currency {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub struct Ticker(Currency, Currency);
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct Ticker(pub Currency, pub Currency);
|
||||
|
||||
impl Ticker {
|
||||
pub fn btc_rate(cur: &str) -> Result<Self> {
|
||||
let to_cur: Currency = cur.parse().map_err(|_| Error::msg(""))?;
|
||||
let to_cur: Currency = cur.parse().map_err(|_| anyhow!("Invalid currency"))?;
|
||||
Ok(Ticker(Currency::BTC, to_cur))
|
||||
}
|
||||
}
|
||||
@ -58,11 +59,55 @@ impl Display for Ticker {
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct TickerRate(pub Ticker, pub f32);
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct CurrencyAmount(pub Currency, pub f32);
|
||||
|
||||
impl TickerRate {
|
||||
pub fn can_convert(&self, currency: Currency) -> bool {
|
||||
currency == self.0 .0 || currency == self.0 .1
|
||||
}
|
||||
|
||||
/// Convert from the source currency into the target currency
|
||||
pub fn convert(&self, source: CurrencyAmount) -> Result<CurrencyAmount> {
|
||||
ensure!(
|
||||
self.can_convert(source.0),
|
||||
"Cant convert, currency doesnt match"
|
||||
);
|
||||
if source.0 == self.0 .0 {
|
||||
Ok(CurrencyAmount(self.0 .1, source.1 * self.1))
|
||||
} else {
|
||||
Ok(CurrencyAmount(self.0 .0, source.1 / self.1))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait ExchangeRateService: Send + Sync {
|
||||
async fn fetch_rates(&self) -> Result<Vec<TickerRate>>;
|
||||
async fn set_rate(&self, ticker: Ticker, amount: f32);
|
||||
async fn get_rate(&self, ticker: Ticker) -> Option<f32>;
|
||||
async fn list_rates(&self) -> Result<Vec<TickerRate>>;
|
||||
}
|
||||
|
||||
/// Get alternative prices based on a source price
|
||||
pub fn alt_prices(rates: &Vec<TickerRate>, source: CurrencyAmount) -> Vec<CurrencyAmount> {
|
||||
let mut ret: Vec<CurrencyAmount> = rates
|
||||
.iter()
|
||||
.filter_map(|r| r.convert(source).ok())
|
||||
.collect();
|
||||
|
||||
let mut ret2 = vec![];
|
||||
for y in rates.iter() {
|
||||
for x in ret.iter() {
|
||||
if let Ok(r1) = y.convert(x.clone()) {
|
||||
if r1.0 != source.0 {
|
||||
ret2.push(r1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ret.append(&mut ret2);
|
||||
ret
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
@ -100,6 +145,11 @@ impl ExchangeRateService for DefaultRateCache {
|
||||
let cache = self.cache.read().await;
|
||||
cache.get(&ticker).cloned()
|
||||
}
|
||||
|
||||
async fn list_rates(&self) -> Result<Vec<TickerRate>> {
|
||||
let cache = self.cache.read().await;
|
||||
Ok(cache.iter().map(|(k, v)| TickerRate(*k, *v)).collect())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
@ -109,3 +159,27 @@ struct MempoolRates {
|
||||
#[serde(rename = "EUR")]
|
||||
pub eur: Option<f32>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
const RATE: f32 = 95_000.0;
|
||||
#[test]
|
||||
fn convert() {
|
||||
let ticker = Ticker::btc_rate("EUR").unwrap();
|
||||
let f = TickerRate(ticker, RATE);
|
||||
|
||||
assert_eq!(
|
||||
f.convert(CurrencyAmount(Currency::EUR, 5.0)).unwrap(),
|
||||
CurrencyAmount(Currency::BTC, 5.0 / RATE)
|
||||
);
|
||||
assert_eq!(
|
||||
f.convert(CurrencyAmount(Currency::BTC, 0.001)).unwrap(),
|
||||
CurrencyAmount(Currency::EUR, RATE * 0.001)
|
||||
);
|
||||
assert!(!f.can_convert(Currency::USD));
|
||||
assert!(f.can_convert(Currency::EUR));
|
||||
assert!(f.can_convert(Currency::BTC));
|
||||
}
|
||||
}
|
||||
|
22
src/mocks.rs
22
src/mocks.rs
@ -80,7 +80,7 @@ impl MockDb {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mock_vm() ->Vm {
|
||||
pub fn mock_vm() -> Vm {
|
||||
let template = Self::mock_template();
|
||||
Vm {
|
||||
id: 1,
|
||||
@ -861,13 +861,13 @@ impl DnsServer for MockDnsServer {
|
||||
}
|
||||
|
||||
pub struct MockExchangeRate {
|
||||
pub rate: Arc<Mutex<f32>>,
|
||||
pub rate: Arc<Mutex<HashMap<Ticker, f32>>>,
|
||||
}
|
||||
|
||||
impl MockExchangeRate {
|
||||
pub fn new(rate: f32) -> Self {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
rate: Arc::new(Mutex::new(rate)),
|
||||
rate: Arc::new(Mutex::new(Default::default())),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -876,16 +876,24 @@ impl MockExchangeRate {
|
||||
impl ExchangeRateService for MockExchangeRate {
|
||||
async fn fetch_rates(&self) -> anyhow::Result<Vec<TickerRate>> {
|
||||
let r = self.rate.lock().await;
|
||||
Ok(vec![TickerRate(Ticker::btc_rate("EUR")?, *r)])
|
||||
Ok(r.iter().map(|(k, v)| TickerRate(k.clone(), *v)).collect())
|
||||
}
|
||||
|
||||
async fn set_rate(&self, ticker: Ticker, amount: f32) {
|
||||
let mut r = self.rate.lock().await;
|
||||
*r = amount;
|
||||
if let Some(v) = r.get_mut(&ticker) {
|
||||
*v += amount;
|
||||
} else {
|
||||
r.insert(ticker, amount);
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_rate(&self, ticker: Ticker) -> Option<f32> {
|
||||
let r = self.rate.lock().await;
|
||||
Some(*r)
|
||||
r.get(&ticker).cloned()
|
||||
}
|
||||
|
||||
async fn list_rates(&self) -> anyhow::Result<Vec<TickerRate>> {
|
||||
self.fetch_rates().await
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::exchange::{ExchangeRateService, Ticker};
|
||||
use anyhow::{bail, Result};
|
||||
use crate::exchange::{Currency, ExchangeRateService, Ticker};
|
||||
use anyhow::{bail, Context, Result};
|
||||
use chrono::{DateTime, Days, Months, TimeDelta, Utc};
|
||||
use ipnetwork::IpNetwork;
|
||||
use lnvps_db::{LNVpsDb, Vm, VmCostPlan, VmCostPlanIntervalType, VmCustomTemplate, VmPayment};
|
||||
@ -86,8 +86,13 @@ impl PricingEngine {
|
||||
let ip4_cost = pricing.ip4_cost * v4s as f32;
|
||||
let ip6_cost = pricing.ip6_cost * v6s as f32;
|
||||
|
||||
let currency: Currency = if let Ok(p) = pricing.currency.parse() {
|
||||
p
|
||||
} else {
|
||||
bail!("Invalid currency")
|
||||
};
|
||||
Ok(PricingData {
|
||||
currency: pricing.currency,
|
||||
currency,
|
||||
cpu_cost,
|
||||
memory_cost,
|
||||
ip6_cost,
|
||||
@ -109,9 +114,7 @@ impl PricingEngine {
|
||||
|
||||
// custom templates are always 1-month intervals
|
||||
let time_value = (vm.expires.add(Months::new(1)) - vm.expires).num_seconds() as u64;
|
||||
let (cost_msats, rate) = self
|
||||
.get_msats_amount(&price.currency, price.total())
|
||||
.await?;
|
||||
let (cost_msats, rate) = self.get_msats_amount(price.currency, price.total()).await?;
|
||||
Ok(CostResult::New {
|
||||
msats: cost_msats,
|
||||
rate,
|
||||
@ -120,8 +123,8 @@ impl PricingEngine {
|
||||
})
|
||||
}
|
||||
|
||||
async fn get_msats_amount(&self, currency: &str, amount: f32) -> Result<(u64, f32)> {
|
||||
let ticker = Ticker::btc_rate(¤cy)?;
|
||||
async fn get_msats_amount(&self, currency: Currency, amount: f32) -> Result<(u64, f32)> {
|
||||
let ticker = Ticker(Currency::BTC, currency);
|
||||
let rate = if let Some(r) = self.rates.get_rate(ticker).await {
|
||||
r
|
||||
} else {
|
||||
@ -157,7 +160,10 @@ impl PricingEngine {
|
||||
let cost_plan = self.db.get_cost_plan(template.cost_plan_id).await?;
|
||||
|
||||
let (cost_msats, rate) = self
|
||||
.get_msats_amount(cost_plan.currency.as_str(), cost_plan.amount)
|
||||
.get_msats_amount(
|
||||
cost_plan.currency.parse().expect("Invalid currency"),
|
||||
cost_plan.amount,
|
||||
)
|
||||
.await?;
|
||||
let time_value = Self::next_template_expire(&vm, &cost_plan);
|
||||
Ok(CostResult::New {
|
||||
@ -188,7 +194,7 @@ pub enum CostResult {
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct PricingData {
|
||||
pub currency: String,
|
||||
pub currency: Currency,
|
||||
pub cpu_cost: f32,
|
||||
pub memory_cost: f32,
|
||||
pub ip4_cost: f32,
|
||||
@ -274,7 +280,9 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn standard_pricing() -> Result<()> {
|
||||
let db = MockDb::default();
|
||||
let rates = Arc::new(MockExchangeRate::new(MOCK_RATE));
|
||||
let rates = Arc::new(MockExchangeRate::new());
|
||||
rates.set_rate(Ticker::btc_rate("EUR")?, MOCK_RATE).await;
|
||||
|
||||
// add basic vm
|
||||
{
|
||||
let mut v = db.vms.lock().await;
|
||||
|
Reference in New Issue
Block a user