feat: payments
feat: spawn vm after payment
This commit is contained in:
@ -11,5 +11,6 @@ mysql = ["sqlx/mysql"]
|
||||
anyhow = "1.0.83"
|
||||
sqlx = { version = "0.8.2", features = ["chrono", "migrate", "runtime-tokio"] }
|
||||
serde = { version = "1.0.213", features = ["derive"] }
|
||||
serde_with = { version = "3.11.0", features = ["macros", "hex"] }
|
||||
chrono = { version = "0.4.38", features = ["serde"] }
|
||||
async-trait = "0.1.83"
|
@ -130,20 +130,24 @@ create table vm_ip_assignment
|
||||
id integer unsigned not null auto_increment primary key,
|
||||
vm_id integer unsigned not null,
|
||||
ip_range_id integer unsigned not null,
|
||||
ip varchar(255) not null,
|
||||
|
||||
constraint fk_vm_ip_assignment_vm foreign key (vm_id) references vm (id),
|
||||
constraint fk_vm_ip_range foreign key (ip_range_id) references ip_range (id)
|
||||
);
|
||||
create unique index ix_vm_ip_assignment_ip on vm_ip_assignment (ip);
|
||||
create table vm_payment
|
||||
(
|
||||
id binary(32) not null,
|
||||
vm_id integer unsigned not null,
|
||||
created timestamp default current_timestamp,
|
||||
expires timestamp not null,
|
||||
amount bigint unsigned not null,
|
||||
invoice varchar(2048) not null,
|
||||
time_value bigint unsigned not null,
|
||||
is_paid bit(1) not null,
|
||||
id binary(32) not null,
|
||||
vm_id integer unsigned not null,
|
||||
created timestamp default current_timestamp,
|
||||
expires timestamp not null,
|
||||
amount bigint unsigned not null,
|
||||
invoice varchar(2048) not null,
|
||||
time_value bigint unsigned not null,
|
||||
is_paid bit(1) not null,
|
||||
settle_index bigint unsigned,
|
||||
|
||||
constraint fk_vm_payment_vm foreign key (vm_id) references vm (id)
|
||||
);
|
||||
);
|
||||
create unique index ix_vm_payment_id on vm_payment (id);
|
@ -45,6 +45,9 @@ pub trait LNVpsDb: Sync + Send {
|
||||
/// List VM's owned by a specific user
|
||||
async fn list_hosts(&self) -> Result<Vec<VmHost>>;
|
||||
|
||||
/// List VM's owned by a specific user
|
||||
async fn get_host(&self, id: u64) -> Result<VmHost>;
|
||||
|
||||
/// Update host resources (usually from [auto_discover])
|
||||
async fn update_host(&self, host: &VmHost) -> Result<()>;
|
||||
|
||||
@ -69,6 +72,9 @@ pub trait LNVpsDb: Sync + Send {
|
||||
/// List VM templates
|
||||
async fn list_vm_templates(&self) -> Result<Vec<VmTemplate>>;
|
||||
|
||||
/// List all VM's
|
||||
async fn list_vms(&self) -> Result<Vec<Vm>>;
|
||||
|
||||
/// List VM's owned by a specific user
|
||||
async fn list_user_vms(&self, id: u64) -> Result<Vec<Vm>>;
|
||||
|
||||
@ -78,15 +84,30 @@ pub trait LNVpsDb: Sync + Send {
|
||||
/// Insert a new VM record
|
||||
async fn insert_vm(&self, vm: &Vm) -> Result<u64>;
|
||||
|
||||
/// List VM ip assignments
|
||||
async fn insert_vm_ip_assignment(&self, ip_assignment: &VmIpAssignment) -> Result<u64>;
|
||||
|
||||
/// List VM ip assignments
|
||||
async fn get_vm_ip_assignments(&self, vm_id: u64) -> Result<Vec<VmIpAssignment>>;
|
||||
|
||||
/// List VM ip assignments by IP range
|
||||
async fn get_vm_ip_assignments_in_range(&self, range_id: u64) -> Result<Vec<VmIpAssignment>>;
|
||||
|
||||
/// List payments by VM id
|
||||
async fn list_vm_payment(&self, vm_id: u64) -> Result<Vec<VmPayment>>;
|
||||
|
||||
/// Insert a new VM payment record
|
||||
async fn insert_vm_payment(&self, vm_payment: &VmPayment) -> Result<u64>;
|
||||
async fn insert_vm_payment(&self, vm_payment: &VmPayment) -> Result<()>;
|
||||
|
||||
/// Get VM payment by payment id
|
||||
async fn get_vm_payment(&self, id: &Vec<u8>) -> Result<VmPayment>;
|
||||
|
||||
/// Update a VM payment record
|
||||
async fn update_vm_payment(&self, vm_payment: &VmPayment) -> Result<()>;
|
||||
|
||||
/// Mark a payment as paid and update the vm expiry
|
||||
async fn vm_payment_paid(&self, id: &VmPayment) -> Result<()>;
|
||||
|
||||
/// Return the most recently settled invoice
|
||||
async fn last_paid_invoice(&self) -> Result<Option<VmPayment>>;
|
||||
}
|
@ -1,14 +1,19 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_with::serde_as;
|
||||
use sqlx::FromRow;
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Serialize, Deserialize, FromRow, Clone, Debug)]
|
||||
/// Users who buy VM's
|
||||
pub struct User {
|
||||
/// Unique ID of this user (database generated)
|
||||
pub id: u64,
|
||||
|
||||
/// The nostr public key for this user
|
||||
#[serde_as(as = "serde_with::hex::Hex")]
|
||||
pub pubkey: Vec<u8>,
|
||||
|
||||
/// When this user first started using the service (first login)
|
||||
pub created: DateTime<Utc>,
|
||||
|
||||
@ -222,16 +227,26 @@ pub struct VmIpAssignment {
|
||||
pub id: u64,
|
||||
pub vm_id: u64,
|
||||
pub ip_range_id: u64,
|
||||
pub ip: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, FromRow, Clone, Debug)]
|
||||
#[serde_as]
|
||||
#[derive(Serialize, Deserialize, FromRow, Clone, Debug, Default)]
|
||||
pub struct VmPayment {
|
||||
pub id: u64,
|
||||
/// Payment hash
|
||||
#[serde_as(as = "serde_with::hex::Hex")]
|
||||
pub id: Vec<u8>,
|
||||
pub vm_id: u64,
|
||||
pub created: DateTime<Utc>,
|
||||
pub expires: DateTime<Utc>,
|
||||
pub amount: u64,
|
||||
pub invoice: String,
|
||||
pub time_value: u64,
|
||||
pub is_paid: bool,
|
||||
|
||||
/// Number of seconds this payment will add to vm expiry
|
||||
#[serde(skip_serializing)]
|
||||
pub time_value: u64,
|
||||
|
||||
#[serde(skip_serializing)]
|
||||
pub settle_index: Option<u64>,
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
use crate::{IpRange, LNVpsDb, User, UserSshKey, Vm, VmCostPlan, VmHost, VmHostDisk, VmHostRegion, VmIpAssignment, VmOsImage, VmPayment, VmTemplate};
|
||||
use anyhow::{Error, Result};
|
||||
use anyhow::{bail, Error, Result};
|
||||
use async_trait::async_trait;
|
||||
use sqlx::{Executor, MySqlPool, Row};
|
||||
|
||||
@ -107,6 +107,14 @@ impl LNVpsDb for LNVpsDbMysql {
|
||||
.map_err(Error::new)
|
||||
}
|
||||
|
||||
async fn get_host(&self, id: u64) -> Result<VmHost> {
|
||||
sqlx::query_as("select * from vm_host where id = ?")
|
||||
.bind(&id)
|
||||
.fetch_one(&self.db)
|
||||
.await
|
||||
.map_err(Error::new)
|
||||
}
|
||||
|
||||
async fn update_host(&self, host: &VmHost) -> Result<()> {
|
||||
sqlx::query("update vm_host set name = ?, cpu = ?, memory = ? where id = ?")
|
||||
.bind(&host.name)
|
||||
@ -171,6 +179,13 @@ impl LNVpsDb for LNVpsDbMysql {
|
||||
.map_err(Error::new)
|
||||
}
|
||||
|
||||
async fn list_vms(&self) -> Result<Vec<Vm>> {
|
||||
sqlx::query_as("select * from vm")
|
||||
.fetch_all(&self.db)
|
||||
.await
|
||||
.map_err(Error::new)
|
||||
}
|
||||
|
||||
async fn list_user_vms(&self, id: u64) -> Result<Vec<Vm>> {
|
||||
sqlx::query_as("select * from vm where user_id = ?")
|
||||
.bind(&id)
|
||||
@ -206,24 +221,44 @@ impl LNVpsDb for LNVpsDbMysql {
|
||||
.try_get(0)?)
|
||||
}
|
||||
|
||||
async fn insert_vm_ip_assignment(&self, ip_assignment: &VmIpAssignment) -> Result<u64> {
|
||||
Ok(sqlx::query("insert into vm_ip_assignment(vm_id,ip_range_id,ip) values(?, ?, ?) returning id")
|
||||
.bind(&ip_assignment.vm_id)
|
||||
.bind(&ip_assignment.ip_range_id)
|
||||
.bind(&ip_assignment.ip)
|
||||
.fetch_one(&self.db)
|
||||
.await
|
||||
.map_err(Error::new)?
|
||||
.try_get(0)?)
|
||||
}
|
||||
|
||||
async fn get_vm_ip_assignments(&self, vm_id: u64) -> Result<Vec<VmIpAssignment>> {
|
||||
sqlx::query_as("select * from vm_ip_assignment where vm_id=?")
|
||||
sqlx::query_as("select * from vm_ip_assignment where vm_id = ?")
|
||||
.bind(vm_id)
|
||||
.fetch_all(&self.db)
|
||||
.await
|
||||
.map_err(Error::new)
|
||||
}
|
||||
|
||||
async fn get_vm_ip_assignments_in_range(&self, range_id: u64) -> Result<Vec<VmIpAssignment>> {
|
||||
sqlx::query_as("select * from vm_ip_assignment where ip_range_id = ?")
|
||||
.bind(range_id)
|
||||
.fetch_all(&self.db)
|
||||
.await
|
||||
.map_err(Error::new)
|
||||
}
|
||||
|
||||
async fn list_vm_payment(&self, vm_id: u64) -> Result<Vec<VmPayment>> {
|
||||
sqlx::query_as("select * from vm_payment where vm_id=?")
|
||||
sqlx::query_as("select * from vm_payment where vm_id = ?")
|
||||
.bind(vm_id)
|
||||
.fetch_all(&self.db)
|
||||
.await
|
||||
.map_err(Error::new)
|
||||
}
|
||||
|
||||
async fn insert_vm_payment(&self, vm_payment: &VmPayment) -> Result<u64> {
|
||||
Ok(sqlx::query("insert into vm_payment(vm_id,created,expires,amount,invoice,time_value,is_paid) values(?,?,?,?,?,?,?) returning id")
|
||||
async fn insert_vm_payment(&self, vm_payment: &VmPayment) -> Result<()> {
|
||||
sqlx::query("insert into vm_payment(id,vm_id,created,expires,amount,invoice,time_value,is_paid) values(?,?,?,?,?,?,?,?)")
|
||||
.bind(&vm_payment.id)
|
||||
.bind(&vm_payment.vm_id)
|
||||
.bind(&vm_payment.created)
|
||||
.bind(&vm_payment.expires)
|
||||
@ -231,10 +266,18 @@ impl LNVpsDb for LNVpsDbMysql {
|
||||
.bind(&vm_payment.invoice)
|
||||
.bind(&vm_payment.time_value)
|
||||
.bind(&vm_payment.is_paid)
|
||||
.execute(&self.db)
|
||||
.await
|
||||
.map_err(Error::new)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_vm_payment(&self, id: &Vec<u8>) -> Result<VmPayment> {
|
||||
sqlx::query_as("select * from vm_payment where id=?")
|
||||
.bind(&id)
|
||||
.fetch_one(&self.db)
|
||||
.await
|
||||
.map_err(Error::new)?
|
||||
.try_get(0)?)
|
||||
.map_err(Error::new)
|
||||
}
|
||||
|
||||
async fn update_vm_payment(&self, vm_payment: &VmPayment) -> Result<()> {
|
||||
@ -246,4 +289,34 @@ impl LNVpsDb for LNVpsDbMysql {
|
||||
.map_err(Error::new)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn vm_payment_paid(&self, vm_payment: &VmPayment) -> Result<()> {
|
||||
if vm_payment.is_paid {
|
||||
bail!("Invoice already paid");
|
||||
}
|
||||
|
||||
let mut tx = self.db.begin().await?;
|
||||
|
||||
sqlx::query("update vm_payment set is_paid = true, settle_index = ? where id = ?")
|
||||
.bind(&vm_payment.settle_index)
|
||||
.bind(&vm_payment.id)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
|
||||
sqlx::query("update vm set expires = TIMESTAMPADD(SECOND, ?, expires) where id = ?")
|
||||
.bind(&vm_payment.time_value)
|
||||
.bind(&vm_payment.vm_id)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
|
||||
tx.commit().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn last_paid_invoice(&self) -> Result<Option<VmPayment>> {
|
||||
sqlx::query_as("select * from vm_payment where is_paid = true order by settle_index desc limit 1")
|
||||
.fetch_optional(&self.db)
|
||||
.await
|
||||
.map_err(Error::new)
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user