feat: custom pricing

closes #3
This commit is contained in:
2025-03-06 21:42:27 +00:00
parent 36ba1f836a
commit 8c3756e3e8
15 changed files with 1242 additions and 359 deletions

View File

@ -139,4 +139,19 @@ pub trait LNVpsDb: Sync + Send {
/// Return the most recently settled invoice
async fn last_paid_invoice(&self) -> Result<Option<VmPayment>>;
/// Return the list of active custom pricing models for a given region
async fn list_custom_pricing(&self, region_id: u64) -> Result<Vec<VmCustomPricing>>;
/// Get a custom pricing model
async fn get_custom_pricing(&self, id: u64) -> Result<VmCustomPricing>;
/// Get a custom pricing model
async fn get_custom_vm_template(&self, id: u64) -> Result<VmCustomTemplate>;
/// Insert custom vm template
async fn insert_custom_vm_template(&self, template: &VmCustomTemplate) -> Result<u64>;
/// Return the list of disk prices for a given custom pricing model
async fn list_custom_pricing_disk(&self, pricing_id: u64) -> Result<Vec<VmCustomPricingDisk>>;
}

View File

@ -92,7 +92,7 @@ pub struct VmHostDisk {
pub enabled: bool,
}
#[derive(Clone, Debug, sqlx::Type, Default, PartialEq, Eq)]
#[derive(Clone, Copy, Debug, sqlx::Type, Default, PartialEq, Eq)]
#[repr(u16)]
pub enum DiskType {
#[default]
@ -100,7 +100,7 @@ pub enum DiskType {
SSD = 1,
}
#[derive(Clone, Debug, sqlx::Type, Default, PartialEq, Eq)]
#[derive(Clone, Copy, Debug, sqlx::Type, Default, PartialEq, Eq)]
#[repr(u16)]
pub enum DiskInterface {
#[default]
@ -109,7 +109,7 @@ pub enum DiskInterface {
PCIe = 2,
}
#[derive(Clone, Debug, sqlx::Type, Default, PartialEq, Eq)]
#[derive(Clone, Copy, Debug, sqlx::Type, Default, PartialEq, Eq)]
#[repr(u16)]
pub enum OsDistribution {
#[default]
@ -203,6 +203,50 @@ pub struct VmTemplate {
pub region_id: u64,
}
/// A custom pricing template, used for billing calculation of a specific VM
/// This mostly just stores the number of resources assigned and the specific pricing used
#[derive(FromRow, Clone, Debug, Default)]
pub struct VmCustomTemplate {
pub id: u64,
pub cpu: u16,
pub memory: u64,
pub disk_size: u64,
pub disk_type: DiskType,
pub disk_interface: DiskInterface,
pub pricing_id: u64,
}
/// Custom pricing template, usually 1 per region
#[derive(FromRow, Clone, Debug, Default)]
pub struct VmCustomPricing {
pub id: u64,
pub name: String,
pub enabled: bool,
pub created: DateTime<Utc>,
pub expires: Option<DateTime<Utc>>,
pub region_id: u64,
pub currency: String,
/// Cost per CPU core
pub cpu_cost: f32,
/// Cost per GB ram
pub memory_cost: f32,
/// Cost per IPv4 address
pub ip4_cost: f32,
/// Cost per IPv6 address
pub ip6_cost: f32,
}
/// Pricing per GB on a disk type (SSD/HDD)
#[derive(FromRow, Clone, Debug, Default)]
pub struct VmCustomPricingDisk {
pub id: u64,
pub pricing_id: u64,
pub kind: DiskType,
pub interface: DiskInterface,
/// Cost as per the currency of the [VmCustomPricing::currency]
pub cost: f32,
}
#[derive(FromRow, Clone, Debug, Default)]
pub struct Vm {
/// Unique VM ID (Same in proxmox)
@ -213,8 +257,10 @@ pub struct Vm {
pub user_id: u64,
/// The base image of this VM
pub image_id: u64,
/// The base image of this VM
pub template_id: u64,
/// The base image of this VM [VmTemplate]
pub template_id: Option<u64>,
/// Custom pricing specification used for this vm [VmCustomTemplate]
pub custom_template_id: Option<u64>,
/// Users ssh-key assigned to this VM
pub ssh_key_id: u64,
/// When the VM was created

View File

@ -1,6 +1,7 @@
use crate::{
IpRange, LNVpsDb, User, UserSshKey, Vm, VmCostPlan, VmHost, VmHostDisk, VmHostRegion,
VmIpAssignment, VmOsImage, VmPayment, VmTemplate,
IpRange, LNVpsDb, User, UserSshKey, Vm, VmCostPlan, VmCustomPricing, VmCustomPricingDisk,
VmCustomTemplate, VmHost, VmHostDisk, VmHostRegion, VmIpAssignment, VmOsImage, VmPayment,
VmTemplate,
};
use anyhow::{bail, Error, Result};
use async_trait::async_trait;
@ -27,7 +28,9 @@ impl LNVpsDbMysql {
#[async_trait]
impl LNVpsDb for LNVpsDbMysql {
async fn migrate(&self) -> Result<()> {
sqlx::migrate!().run(&self.db).await.map_err(Error::new)
let migrator = sqlx::migrate!();
migrator.run(&self.db).await.map_err(Error::new)?;
Ok(())
}
async fn upsert_user(&self, pubkey: &[u8; 32]) -> Result<u64> {
@ -210,7 +213,7 @@ impl LNVpsDb for LNVpsDbMysql {
}
async fn list_vm_templates(&self) -> Result<Vec<VmTemplate>> {
sqlx::query_as("select * from vm_template")
sqlx::query_as("select * from vm_template where enabled = 1")
.fetch_all(&self.db)
.await
.map_err(Error::new)
@ -219,14 +222,14 @@ impl LNVpsDb for LNVpsDbMysql {
async fn insert_vm_template(&self, template: &VmTemplate) -> Result<u64> {
Ok(sqlx::query("insert into vm_template(name,enabled,created,expires,cpu,memory,disk_size,disk_type,disk_interface,cost_plan_id,region_id) values(?,?,?,?,?,?,?,?,?,?,?) returning id")
.bind(&template.name)
.bind(&template.enabled)
.bind(&template.created)
.bind(&template.expires)
.bind(template.enabled)
.bind(template.created)
.bind(template.expires)
.bind(template.cpu)
.bind(template.memory)
.bind(template.disk_size)
.bind(&template.disk_type)
.bind(&template.disk_interface)
.bind(template.disk_type)
.bind(template.disk_interface)
.bind(template.cost_plan_id)
.bind(template.region_id)
.fetch_one(&self.db)
@ -274,11 +277,12 @@ impl LNVpsDb for LNVpsDbMysql {
}
async fn insert_vm(&self, vm: &Vm) -> Result<u64> {
Ok(sqlx::query("insert into vm(host_id,user_id,image_id,template_id,ssh_key_id,created,expires,disk_id,mac_address,ref_code) values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?) returning id")
Ok(sqlx::query("insert into vm(host_id,user_id,image_id,template_id,custom_template_id,ssh_key_id,created,expires,disk_id,mac_address,ref_code) values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?,?) returning id")
.bind(vm.host_id)
.bind(vm.user_id)
.bind(vm.image_id)
.bind(vm.template_id)
.bind(vm.custom_template_id)
.bind(vm.ssh_key_id)
.bind(vm.created)
.bind(vm.expires)
@ -343,7 +347,7 @@ impl LNVpsDb for LNVpsDbMysql {
.bind(&ip_assignment.dns_forward_ref)
.bind(&ip_assignment.dns_reverse)
.bind(&ip_assignment.dns_reverse_ref)
.bind(&ip_assignment.id)
.bind(ip_assignment.id)
.execute(&self.db)
.await
.map_err(Error::new)?;
@ -368,7 +372,7 @@ impl LNVpsDb for LNVpsDbMysql {
async fn delete_vm_ip_assignment(&self, vm_id: u64) -> Result<()> {
sqlx::query("update vm_ip_assignment set deleted = 1 where vm_id = ?")
.bind(&vm_id)
.bind(vm_id)
.execute(&self.db)
.await?;
Ok(())
@ -448,4 +452,50 @@ impl LNVpsDb for LNVpsDbMysql {
.await
.map_err(Error::new)
}
async fn list_custom_pricing(&self, region_id: u64) -> Result<Vec<VmCustomPricing>> {
sqlx::query_as("select * from vm_custom_pricing where region_id = ? and enabled = 1")
.bind(region_id)
.fetch_all(&self.db)
.await
.map_err(Error::new)
}
async fn get_custom_pricing(&self, id: u64) -> Result<VmCustomPricing> {
sqlx::query_as("select * from vm_custom_pricing where id=?")
.bind(id)
.fetch_one(&self.db)
.await
.map_err(Error::new)
}
async fn get_custom_vm_template(&self, id: u64) -> Result<VmCustomTemplate> {
sqlx::query_as("select * from vm_custom_template where id=?")
.bind(id)
.fetch_one(&self.db)
.await
.map_err(Error::new)
}
async fn insert_custom_vm_template(&self, template: &VmCustomTemplate) -> Result<u64> {
Ok(sqlx::query("insert into vm_custom_template(cpu,memory,disk_size,disk_type,disk_interface,pricing_id) values(?,?,?,?,?,?) returning id")
.bind(template.cpu)
.bind(template.memory)
.bind(template.disk_size)
.bind(template.disk_type)
.bind(template.disk_interface)
.bind(template.pricing_id)
.fetch_one(&self.db)
.await
.map_err(Error::new)?
.try_get(0)?)
}
async fn list_custom_pricing_disk(&self, pricing_id: u64) -> Result<Vec<VmCustomPricingDisk>> {
sqlx::query_as("select * from vm_custom_pricing_disk where pricing_id=?")
.bind(pricing_id)
.fetch_all(&self.db)
.await
.map_err(Error::new)
}
}