feat: alt prices
This commit is contained in:
@ -4,7 +4,10 @@ use crate::status::VmState;
|
||||
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};
|
||||
@ -141,16 +144,12 @@ impl ApiTemplatesResponse {
|
||||
let rates = rates.list_rates().await?;
|
||||
|
||||
for mut template in &mut self.templates {
|
||||
if let Some(list_price) = template.cost_plan.price.first() {
|
||||
for alt_price in alt_prices(
|
||||
&rates,
|
||||
CurrencyAmount(list_price.currency, list_price.amount),
|
||||
) {
|
||||
template.cost_plan.price.push(ApiPrice {
|
||||
currency: alt_price.0,
|
||||
amount: alt_price.1,
|
||||
});
|
||||
}
|
||||
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(())
|
||||
@ -170,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,
|
||||
@ -215,6 +248,15 @@ pub struct ApiPrice {
|
||||
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,
|
||||
|
@ -1,9 +1,10 @@
|
||||
use crate::api::model::{
|
||||
AccountPatchRequest, ApiPrice, 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?;
|
||||
|
||||
@ -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
|
||||
|
@ -4,7 +4,7 @@ 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;
|
||||
@ -91,11 +91,23 @@ pub trait ExchangeRateService: Send + Sync {
|
||||
|
||||
/// Get alternative prices based on a source price
|
||||
pub fn alt_prices(rates: &Vec<TickerRate>, source: CurrencyAmount) -> Vec<CurrencyAmount> {
|
||||
// TODO: return all alt prices by cross-converting all currencies
|
||||
rates
|
||||
let mut ret: Vec<CurrencyAmount> = rates
|
||||
.iter()
|
||||
.filter_map(|r| r.convert(source).ok())
|
||||
.collect()
|
||||
.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)]
|
||||
|
Reference in New Issue
Block a user