feat: nostr domain hosting
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
20
Cargo.lock
generated
20
Cargo.lock
generated
@ -2038,7 +2038,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856"
|
||||
|
||||
[[package]]
|
||||
name = "lnvps"
|
||||
name = "lnvps_api"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
@ -2054,6 +2054,7 @@ dependencies = [
|
||||
"ipnetwork",
|
||||
"isocountry",
|
||||
"lettre",
|
||||
"lnvps_common",
|
||||
"lnvps_db",
|
||||
"log",
|
||||
"native-tls",
|
||||
@ -2079,6 +2080,13 @@ dependencies = [
|
||||
"virt",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lnvps_common"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"rocket",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lnvps_db"
|
||||
version = "0.1.0"
|
||||
@ -2094,7 +2102,17 @@ dependencies = [
|
||||
name = "lnvps_nostr"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"config",
|
||||
"env_logger",
|
||||
"hex",
|
||||
"lnvps_common",
|
||||
"lnvps_db",
|
||||
"log",
|
||||
"rocket",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
12
Cargo.toml
12
Cargo.toml
@ -3,11 +3,17 @@ resolver = "3"
|
||||
members = [
|
||||
"lnvps_db",
|
||||
"lnvps_api",
|
||||
"lnvps_nostr"
|
||||
"lnvps_nostr",
|
||||
"lnvps_common"
|
||||
]
|
||||
|
||||
[workspace.dependencies]
|
||||
tokio = { version = "1.37.0", features = ["rt", "rt-multi-thread", "macros", "sync", "io-util"] }
|
||||
tokio = { version = "1.37.0", features = ["rt", "rt-multi-thread", "macros"] }
|
||||
anyhow = "1.0.83"
|
||||
log = "0.4.21"
|
||||
env_logger = "0.11.7"
|
||||
env_logger = "0.11.7"
|
||||
serde = { version = "1.0.213", features = ["derive"] }
|
||||
serde_json = "1.0.132"
|
||||
rocket = { version = "0.5.1", features = ["json"] }
|
||||
config = { version = "0.15.8", features = ["yaml"] }
|
||||
hex = "0.4.3"
|
@ -4,9 +4,11 @@ FROM $IMAGE AS build
|
||||
WORKDIR /app/src
|
||||
COPY . .
|
||||
RUN apt update && apt -y install protobuf-compiler libvirt-dev
|
||||
RUN cargo test && cargo install --path lnvps_api --root /app/build
|
||||
RUN cargo test \
|
||||
&& cargo install --root /app/build --path lnvps_api \
|
||||
&& cargo install --root /app/build --path lnvps_nostr
|
||||
|
||||
FROM $IMAGE AS runner
|
||||
WORKDIR /app
|
||||
COPY --from=build /app/build .
|
||||
ENTRYPOINT ["./bin/api"]
|
||||
ENTRYPOINT ["./bin/lnvps_api"]
|
@ -1,25 +1,30 @@
|
||||
[package]
|
||||
name = "lnvps"
|
||||
name = "lnvps_api"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[[bin]]
|
||||
name = "api"
|
||||
name = "lnvps_api"
|
||||
path = "src/bin/api.rs"
|
||||
|
||||
[features]
|
||||
default = [
|
||||
"mikrotik",
|
||||
"nostr-dm",
|
||||
"nostr-dvm",
|
||||
"nostr-domain",
|
||||
"proxmox",
|
||||
"lnd",
|
||||
"cloudflare",
|
||||
"revolut",
|
||||
"bitvora"
|
||||
"bitvora",
|
||||
"tokio/sync",
|
||||
"tokio/io-util"
|
||||
]
|
||||
mikrotik = ["dep:reqwest"]
|
||||
nostr-dm = ["dep:nostr-sdk"]
|
||||
nostr-dvm = ["dep:nostr-sdk"]
|
||||
nostr-domain = ["lnvps_db/nostr-domain"]
|
||||
proxmox = ["dep:reqwest", "dep:ssh2", "dep:tokio-tungstenite"]
|
||||
libvirt = ["dep:virt", "dep:uuid", "dep:quick-xml"]
|
||||
lnd = ["dep:fedimint-tonic-lnd"]
|
||||
@ -29,15 +34,16 @@ revolut = ["dep:reqwest", "dep:sha2", "dep:hmac"]
|
||||
|
||||
[dependencies]
|
||||
lnvps_db = { path = "../lnvps_db" }
|
||||
lnvps_common = { path = "../lnvps_common" }
|
||||
anyhow.workspace = true
|
||||
log.workspace = true
|
||||
env_logger.workspace = true
|
||||
|
||||
tokio = { version = "1.37.0", features = ["rt", "rt-multi-thread", "macros", "sync", "io-util"] }
|
||||
config = { version = "0.15.8", features = ["yaml"] }
|
||||
serde = { version = "1.0.213", features = ["derive"] }
|
||||
serde_json = "1.0.132"
|
||||
rocket = { version = "0.5.1", features = ["json"] }
|
||||
tokio.workspace = true
|
||||
config.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
rocket.workspace = true
|
||||
hex.workspace = true
|
||||
rocket_okapi = { version = "0.9.0", features = ["swagger"] }
|
||||
schemars = { version = "0.8.22", features = ["chrono"] }
|
||||
chrono = { version = "0.4.38", features = ["serde"] }
|
||||
@ -50,7 +56,7 @@ ssh-key = "0.6.7"
|
||||
lettre = { version = "0.11.10", features = ["tokio1-native-tls"] }
|
||||
ws = { package = "rocket_ws", version = "0.1.1" }
|
||||
native-tls = "0.2.12"
|
||||
hex = "0.4.3"
|
||||
|
||||
futures = "0.3.31"
|
||||
isocountry = "0.3.2"
|
||||
|
||||
|
@ -1,6 +1,8 @@
|
||||
use rocket::Route;
|
||||
|
||||
mod model;
|
||||
#[cfg(feature = "nostr-domain")]
|
||||
mod nostr_domain;
|
||||
mod routes;
|
||||
mod webhook;
|
||||
|
||||
|
@ -15,7 +15,7 @@ use std::collections::HashMap;
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Serialize, JsonSchema)]
|
||||
pub struct ApiVmStatus {
|
||||
/// Unique VM ID (Same in proxmox)
|
||||
pub id: u64,
|
||||
@ -37,7 +37,7 @@ pub struct ApiVmStatus {
|
||||
pub status: VmState,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Serialize, JsonSchema)]
|
||||
pub struct ApiUserSshKey {
|
||||
pub id: u64,
|
||||
pub name: String,
|
||||
@ -54,7 +54,7 @@ impl From<lnvps_db::UserSshKey> for ApiUserSshKey {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Serialize, JsonSchema)]
|
||||
pub struct ApiVmIpAssignment {
|
||||
pub id: u64,
|
||||
pub ip: String,
|
||||
@ -133,7 +133,7 @@ impl From<DiskInterface> for lnvps_db::DiskInterface {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Serialize, JsonSchema)]
|
||||
pub struct ApiTemplatesResponse {
|
||||
pub templates: Vec<ApiVmTemplate>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
@ -157,7 +157,7 @@ impl ApiTemplatesResponse {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
#[derive(Serialize, Deserialize, JsonSchema)]
|
||||
#[derive(Serialize, JsonSchema)]
|
||||
pub struct ApiCustomTemplateParams {
|
||||
pub id: u64,
|
||||
pub name: String,
|
||||
@ -196,8 +196,7 @@ impl ApiCustomTemplateParams {
|
||||
.filter_map(|d| {
|
||||
Some(ApiCustomTemplateDiskParam {
|
||||
min_disk: GB * 5,
|
||||
max_disk: *max_disk
|
||||
.get(&(d.kind.into(), d.interface.into()))?,
|
||||
max_disk: *max_disk.get(&(d.kind.into(), d.interface.into()))?,
|
||||
disk_type: d.kind.into(),
|
||||
disk_interface: d.interface.into(),
|
||||
})
|
||||
|
202
lnvps_api/src/api/nostr_domain.rs
Normal file
202
lnvps_api/src/api/nostr_domain.rs
Normal file
@ -0,0 +1,202 @@
|
||||
use crate::api::routes::{ApiData, ApiResult};
|
||||
use crate::nip98::Nip98Auth;
|
||||
use crate::settings::Settings;
|
||||
use chrono::{DateTime, Utc};
|
||||
use lnvps_db::{LNVPSNostrDb, LNVpsDb, NostrDomain, NostrDomainHandle};
|
||||
use rocket::serde::json::Json;
|
||||
use rocket::serde::{Deserialize, Serialize};
|
||||
use rocket::{delete, get, post, routes, Route, State};
|
||||
use rocket_okapi::okapi::openapi3::OpenApi;
|
||||
use rocket_okapi::settings::OpenApiSettings;
|
||||
use rocket_okapi::{openapi, openapi_get_routes, openapi_routes, JsonSchema};
|
||||
use std::sync::Arc;
|
||||
|
||||
pub fn routes() -> Vec<Route> {
|
||||
routes![
|
||||
v1_nostr_domains,
|
||||
v1_create_nostr_domain,
|
||||
v1_list_nostr_domain_handles,
|
||||
v1_create_nostr_domain_handle,
|
||||
v1_delete_nostr_domain_handle
|
||||
]
|
||||
}
|
||||
|
||||
#[openapi(tag = "NIP05")]
|
||||
#[get("/api/v1/nostr/domain")]
|
||||
async fn v1_nostr_domains(
|
||||
auth: Nip98Auth,
|
||||
db: &State<Arc<dyn LNVpsDb>>,
|
||||
settings: &State<Settings>,
|
||||
) -> ApiResult<ApiDomainsResponse> {
|
||||
let pubkey = auth.event.pubkey.to_bytes();
|
||||
let uid = db.upsert_user(&pubkey).await?;
|
||||
|
||||
let domains = db.list_domains(uid).await?;
|
||||
ApiData::ok(ApiDomainsResponse {
|
||||
domains: domains.into_iter().map(|d| d.into()).collect(),
|
||||
cname: settings.nostr_address_host.clone().unwrap_or_default(),
|
||||
})
|
||||
}
|
||||
|
||||
#[openapi(tag = "NIP05")]
|
||||
#[post("/api/v1/nostr/domain", format = "json", data = "<data>")]
|
||||
async fn v1_create_nostr_domain(
|
||||
auth: Nip98Auth,
|
||||
db: &State<Arc<dyn LNVpsDb>>,
|
||||
data: Json<NameRequest>,
|
||||
) -> ApiResult<ApiNostrDomain> {
|
||||
let pubkey = auth.event.pubkey.to_bytes();
|
||||
let uid = db.upsert_user(&pubkey).await?;
|
||||
|
||||
let mut dom = NostrDomain {
|
||||
owner_id: uid,
|
||||
name: data.name.clone(),
|
||||
..Default::default()
|
||||
};
|
||||
let dom_id = db.insert_domain(&dom).await?;
|
||||
dom.id = dom_id;
|
||||
|
||||
ApiData::ok(dom.into())
|
||||
}
|
||||
|
||||
#[openapi(tag = "NIP05")]
|
||||
#[get("/api/v1/nostr/domain/<dom>/handle")]
|
||||
async fn v1_list_nostr_domain_handles(
|
||||
auth: Nip98Auth,
|
||||
db: &State<Arc<dyn LNVpsDb>>,
|
||||
dom: u64,
|
||||
) -> ApiResult<Vec<ApiNostrDomainHandle>> {
|
||||
let pubkey = auth.event.pubkey.to_bytes();
|
||||
let uid = db.upsert_user(&pubkey).await?;
|
||||
|
||||
let domain = db.get_domain(dom).await?;
|
||||
if domain.owner_id != uid {
|
||||
return ApiData::err("Access denied");
|
||||
}
|
||||
|
||||
let handles = db.list_handles(domain.id).await?;
|
||||
ApiData::ok(handles.into_iter().map(|h| h.into()).collect())
|
||||
}
|
||||
|
||||
#[openapi(tag = "NIP05")]
|
||||
#[post("/api/v1/nostr/domain/<dom>/handle", format = "json", data = "<data>")]
|
||||
async fn v1_create_nostr_domain_handle(
|
||||
auth: Nip98Auth,
|
||||
db: &State<Arc<dyn LNVpsDb>>,
|
||||
dom: u64,
|
||||
data: Json<HandleRequest>,
|
||||
) -> ApiResult<ApiNostrDomainHandle> {
|
||||
let pubkey = auth.event.pubkey.to_bytes();
|
||||
let uid = db.upsert_user(&pubkey).await?;
|
||||
|
||||
let domain = db.get_domain(dom).await?;
|
||||
if domain.owner_id != uid {
|
||||
return ApiData::err("Access denied");
|
||||
}
|
||||
|
||||
let h_pubkey = hex::decode(&data.pubkey)?;
|
||||
if h_pubkey.len() != 32 {
|
||||
return ApiData::err("Invalid public key");
|
||||
}
|
||||
|
||||
let mut handle = NostrDomainHandle {
|
||||
domain_id: domain.id,
|
||||
handle: data.name.clone(),
|
||||
pubkey: h_pubkey,
|
||||
..Default::default()
|
||||
};
|
||||
let id = db.insert_handle(&handle).await?;
|
||||
handle.id = id;
|
||||
|
||||
ApiData::ok(handle.into())
|
||||
}
|
||||
|
||||
#[openapi(tag = "NIP05")]
|
||||
#[delete("/api/v1/nostr/domain/<dom>/handle/<handle>")]
|
||||
async fn v1_delete_nostr_domain_handle(
|
||||
auth: Nip98Auth,
|
||||
db: &State<Arc<dyn LNVpsDb>>,
|
||||
dom: u64,
|
||||
handle: u64,
|
||||
) -> ApiResult<()> {
|
||||
let pubkey = auth.event.pubkey.to_bytes();
|
||||
let uid = db.upsert_user(&pubkey).await?;
|
||||
|
||||
let domain = db.get_domain(dom).await?;
|
||||
if domain.owner_id != uid {
|
||||
return ApiData::err("Access denied");
|
||||
}
|
||||
db.delete_handle(handle).await?;
|
||||
ApiData::ok(())
|
||||
}
|
||||
|
||||
#[derive(Deserialize, JsonSchema)]
|
||||
struct NameRequest {
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, JsonSchema)]
|
||||
struct HandleRequest {
|
||||
pub pubkey: String,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, JsonSchema)]
|
||||
struct ApiNostrDomain {
|
||||
pub id: u64,
|
||||
pub name: String,
|
||||
pub enabled: bool,
|
||||
pub handles: u64,
|
||||
pub created: DateTime<Utc>,
|
||||
pub relays: Vec<String>,
|
||||
}
|
||||
|
||||
impl From<NostrDomain> for ApiNostrDomain {
|
||||
fn from(value: NostrDomain) -> Self {
|
||||
Self {
|
||||
id: value.id,
|
||||
name: value.name,
|
||||
enabled: value.enabled,
|
||||
handles: value.handles as u64,
|
||||
created: value.created,
|
||||
relays: if let Some(r) = value.relays {
|
||||
r.split(',').map(|s| s.to_string()).collect()
|
||||
} else {
|
||||
vec![]
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, JsonSchema)]
|
||||
struct ApiNostrDomainHandle {
|
||||
pub id: u64,
|
||||
pub domain_id: u64,
|
||||
pub handle: String,
|
||||
pub created: DateTime<Utc>,
|
||||
pub pubkey: String,
|
||||
pub relays: Vec<String>,
|
||||
}
|
||||
|
||||
impl From<NostrDomainHandle> for ApiNostrDomainHandle {
|
||||
fn from(value: NostrDomainHandle) -> Self {
|
||||
Self {
|
||||
id: value.id,
|
||||
domain_id: value.domain_id,
|
||||
created: value.created,
|
||||
handle: value.handle,
|
||||
pubkey: hex::encode(value.pubkey),
|
||||
relays: if let Some(r) = value.relays {
|
||||
r.split(',').map(|s| s.to_string()).collect()
|
||||
} else {
|
||||
vec![]
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, JsonSchema)]
|
||||
struct ApiDomainsResponse {
|
||||
pub domains: Vec<ApiNostrDomain>,
|
||||
pub cname: String,
|
||||
}
|
@ -25,7 +25,7 @@ use rocket::{get, patch, post, routes, Responder, Route, State};
|
||||
use rocket_okapi::gen::OpenApiGenerator;
|
||||
use rocket_okapi::okapi::openapi3::Responses;
|
||||
use rocket_okapi::response::OpenApiResponderInner;
|
||||
use rocket_okapi::{openapi, openapi_get_routes};
|
||||
use rocket_okapi::{openapi, openapi_get_routes, openapi_routes};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use ssh_key::PublicKey;
|
||||
@ -38,7 +38,7 @@ use tokio::sync::mpsc::{Sender, UnboundedSender};
|
||||
pub fn routes() -> Vec<Route> {
|
||||
let mut routes = vec![];
|
||||
|
||||
routes.append(&mut openapi_get_routes![
|
||||
let mut api_routes = openapi_get_routes![
|
||||
v1_get_account,
|
||||
v1_patch_account,
|
||||
v1_list_vms,
|
||||
@ -59,17 +59,20 @@ pub fn routes() -> Vec<Route> {
|
||||
v1_custom_template_calc,
|
||||
v1_create_custom_vm_order,
|
||||
v1_get_payment_methods
|
||||
]);
|
||||
];
|
||||
#[cfg(feature = "nostr-domain")]
|
||||
api_routes.append(&mut super::nostr_domain::routes());
|
||||
routes.append(&mut api_routes);
|
||||
|
||||
routes.append(&mut routes![v1_terminal_proxy]);
|
||||
|
||||
routes
|
||||
}
|
||||
|
||||
type ApiResult<T> = Result<Json<ApiData<T>>, ApiError>;
|
||||
pub type ApiResult<T> = Result<Json<ApiData<T>>, ApiError>;
|
||||
|
||||
#[derive(Serialize, Deserialize, JsonSchema)]
|
||||
struct ApiData<T: Serialize> {
|
||||
pub struct ApiData<T: Serialize> {
|
||||
pub data: T,
|
||||
}
|
||||
|
||||
@ -84,7 +87,7 @@ impl<T: Serialize> ApiData<T> {
|
||||
|
||||
#[derive(Serialize, Deserialize, JsonSchema, Responder)]
|
||||
#[response(status = 500)]
|
||||
struct ApiError {
|
||||
pub struct ApiError {
|
||||
pub error: String,
|
||||
}
|
||||
|
||||
|
@ -1,16 +1,16 @@
|
||||
use anyhow::Error;
|
||||
use clap::Parser;
|
||||
use config::{Config, File};
|
||||
use lnvps::api;
|
||||
use lnvps::cors::CORS;
|
||||
use lnvps::data_migration::run_data_migrations;
|
||||
use lnvps::dvm::start_dvms;
|
||||
use lnvps::exchange::{DefaultRateCache, ExchangeRateService};
|
||||
use lnvps::lightning::get_node;
|
||||
use lnvps::payments::listen_all_payments;
|
||||
use lnvps::settings::Settings;
|
||||
use lnvps::status::VmStateCache;
|
||||
use lnvps::worker::{WorkJob, Worker};
|
||||
use lnvps_api::api;
|
||||
use lnvps_api::data_migration::run_data_migrations;
|
||||
use lnvps_api::dvm::start_dvms;
|
||||
use lnvps_api::exchange::{DefaultRateCache, ExchangeRateService};
|
||||
use lnvps_api::lightning::get_node;
|
||||
use lnvps_api::payments::listen_all_payments;
|
||||
use lnvps_api::settings::Settings;
|
||||
use lnvps_api::status::VmStateCache;
|
||||
use lnvps_api::worker::{WorkJob, Worker};
|
||||
use lnvps_common::CORS;
|
||||
use lnvps_db::{LNVpsDb, LNVpsDbMysql};
|
||||
use log::error;
|
||||
use nostr::Keys;
|
||||
@ -168,7 +168,7 @@ async fn main() -> Result<(), Error> {
|
||||
.launch()
|
||||
.await
|
||||
{
|
||||
error!("{}", e);
|
||||
error!("{:?}", e);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@ -1,13 +1,13 @@
|
||||
use crate::data_migration::DataMigration;
|
||||
use crate::provisioner::{LNVpsProvisioner, NetworkProvisioner};
|
||||
use chrono::Utc;
|
||||
use ipnetwork::IpNetwork;
|
||||
use lnvps_db::LNVpsDb;
|
||||
use log::info;
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
use chrono::Utc;
|
||||
use log::info;
|
||||
|
||||
pub struct Ip6InitDataMigration {
|
||||
db: Arc<dyn LNVpsDb>,
|
||||
@ -35,11 +35,10 @@ impl DataMigration for Ip6InitDataMigration {
|
||||
let ips = db.list_vm_ip_assignments(vm.id).await?;
|
||||
// if no ipv6 address is picked already pick one
|
||||
if ips.iter().all(|i| {
|
||||
IpNetwork::from_str(&i.ip)
|
||||
.map(|i| i.is_ipv4())
|
||||
.unwrap_or(false)
|
||||
})
|
||||
{
|
||||
IpNetwork::from_str(&i.ip)
|
||||
.map(|i| i.is_ipv4())
|
||||
.unwrap_or(false)
|
||||
}) {
|
||||
let ips_pick = net.pick_ip_for_region(host.region_id).await?;
|
||||
if let Some(mut v6) = ips_pick.ip6 {
|
||||
info!("Assigning ip {} to vm {}", v6.ip, vm.id);
|
||||
|
@ -1,4 +1,6 @@
|
||||
use crate::data_migration::dns::DnsDataMigration;
|
||||
use crate::data_migration::ip6_init::Ip6InitDataMigration;
|
||||
use crate::provisioner::LNVpsProvisioner;
|
||||
use crate::settings::Settings;
|
||||
use anyhow::Result;
|
||||
use lnvps_db::LNVpsDb;
|
||||
@ -6,8 +8,6 @@ use log::{error, info};
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
use crate::data_migration::ip6_init::Ip6InitDataMigration;
|
||||
use crate::provisioner::LNVpsProvisioner;
|
||||
|
||||
mod dns;
|
||||
mod ip6_init;
|
||||
@ -17,9 +17,16 @@ pub trait DataMigration: Send + Sync {
|
||||
fn migrate(&self) -> Pin<Box<dyn Future<Output = Result<()>> + Send>>;
|
||||
}
|
||||
|
||||
pub async fn run_data_migrations(db: Arc<dyn LNVpsDb>, lnvps: Arc<LNVpsProvisioner>, settings: &Settings) -> Result<()> {
|
||||
pub async fn run_data_migrations(
|
||||
db: Arc<dyn LNVpsDb>,
|
||||
lnvps: Arc<LNVpsProvisioner>,
|
||||
settings: &Settings,
|
||||
) -> Result<()> {
|
||||
let mut migrations: Vec<Box<dyn DataMigration>> = vec![];
|
||||
migrations.push(Box::new(Ip6InitDataMigration::new(db.clone(), lnvps.clone())));
|
||||
migrations.push(Box::new(Ip6InitDataMigration::new(
|
||||
db.clone(),
|
||||
lnvps.clone(),
|
||||
)));
|
||||
|
||||
if let Some(d) = DnsDataMigration::new(db.clone(), settings) {
|
||||
migrations.push(Box::new(d));
|
||||
|
@ -6,9 +6,9 @@ use std::str::FromStr;
|
||||
|
||||
#[cfg(feature = "cloudflare")]
|
||||
mod cloudflare;
|
||||
use crate::provisioner::NetworkProvisioner;
|
||||
#[cfg(feature = "cloudflare")]
|
||||
pub use cloudflare::*;
|
||||
use crate::provisioner::NetworkProvisioner;
|
||||
|
||||
#[async_trait]
|
||||
pub trait DnsServer: Send + Sync {
|
||||
|
@ -221,10 +221,13 @@ pub struct VmHostDiskInfo {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use chrono::Utc;
|
||||
use lnvps_db::{DiskInterface, DiskType, IpRange, IpRangeAllocationMode, OsDistribution, UserSshKey, Vm, VmHost, VmHostDisk, VmIpAssignment, VmOsImage, VmTemplate};
|
||||
use crate::{GB, TB};
|
||||
use crate::host::FullVmInfo;
|
||||
use crate::{GB, TB};
|
||||
use chrono::Utc;
|
||||
use lnvps_db::{
|
||||
DiskInterface, DiskType, IpRange, IpRangeAllocationMode, OsDistribution, UserSshKey, Vm,
|
||||
VmHost, VmHostDisk, VmIpAssignment, VmOsImage, VmTemplate,
|
||||
};
|
||||
|
||||
pub fn mock_full_vm() -> FullVmInfo {
|
||||
let template = VmTemplate {
|
||||
@ -367,4 +370,4 @@ mod tests {
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
pub mod api;
|
||||
pub mod cors;
|
||||
pub mod data_migration;
|
||||
pub mod dns;
|
||||
pub mod exchange;
|
||||
|
@ -11,10 +11,10 @@ use anyhow::{anyhow, bail, ensure, Context};
|
||||
use chrono::{DateTime, TimeDelta, Utc};
|
||||
use fedimint_tonic_lnd::tonic::codegen::tokio_stream::Stream;
|
||||
use lnvps_db::{
|
||||
async_trait, AccessPolicy, DiskInterface, DiskType, IpRange, IpRangeAllocationMode, LNVpsDb,
|
||||
OsDistribution, User, UserSshKey, Vm, VmCostPlan, VmCostPlanIntervalType, VmCustomPricing,
|
||||
VmCustomPricingDisk, VmCustomTemplate, VmHost, VmHostDisk, VmHostKind, VmHostRegion,
|
||||
VmIpAssignment, VmOsImage, VmPayment, VmTemplate,
|
||||
async_trait, AccessPolicy, DiskInterface, DiskType, IpRange, IpRangeAllocationMode,
|
||||
LNVPSNostrDb, LNVpsDb, NostrDomain, NostrDomainHandle, OsDistribution, User, UserSshKey, Vm,
|
||||
VmCostPlan, VmCostPlanIntervalType, VmCustomPricing, VmCustomPricingDisk, VmCustomTemplate,
|
||||
VmHost, VmHostDisk, VmHostKind, VmHostRegion, VmIpAssignment, VmOsImage, VmPayment, VmTemplate,
|
||||
};
|
||||
use std::collections::HashMap;
|
||||
use std::ops::Add;
|
||||
@ -206,6 +206,57 @@ impl Default for MockDb {
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl LNVPSNostrDb for MockDb {
|
||||
async fn get_handle(&self, handle_id: u64) -> anyhow::Result<NostrDomainHandle> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
async fn get_handle_by_name(
|
||||
&self,
|
||||
domain_id: u64,
|
||||
handle: &str,
|
||||
) -> anyhow::Result<NostrDomainHandle> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
async fn insert_handle(&self, handle: &NostrDomainHandle) -> anyhow::Result<u64> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
async fn update_handle(&self, handle: &NostrDomainHandle) -> anyhow::Result<()> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
async fn delete_handle(&self, handle_id: u64) -> anyhow::Result<()> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
async fn list_handles(&self, domain_id: u64) -> anyhow::Result<Vec<NostrDomainHandle>> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
async fn get_domain(&self, id: u64) -> anyhow::Result<NostrDomain> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
async fn get_domain_by_name(&self, name: &str) -> anyhow::Result<NostrDomain> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
async fn list_domains(&self, owner_id: u64) -> anyhow::Result<Vec<NostrDomain>> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
async fn insert_domain(&self, domain: &NostrDomain) -> anyhow::Result<u64> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
async fn delete_domain(&self, domain_id: u64) -> anyhow::Result<()> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl LNVpsDb for MockDb {
|
||||
async fn migrate(&self) -> anyhow::Result<()> {
|
||||
|
@ -30,7 +30,10 @@ pub struct ArpEntry {
|
||||
|
||||
impl ArpEntry {
|
||||
pub fn new(vm: &Vm, ip: &VmIpAssignment, interface: Option<String>) -> Result<Self> {
|
||||
ensure!(vm.mac_address != "ff:ff:ff:ff:ff:ff", "MAC address is invalid because its blank");
|
||||
ensure!(
|
||||
vm.mac_address != "ff:ff:ff:ff:ff:ff",
|
||||
"MAC address is invalid because its blank"
|
||||
);
|
||||
Ok(Self {
|
||||
id: ip.arp_ref.clone(),
|
||||
address: ip.ip.clone(),
|
||||
|
@ -351,5 +351,5 @@ enum OvhTaskFunction {
|
||||
MoveVirtualMac,
|
||||
VirtualMacAdd,
|
||||
VirtualMacDelete,
|
||||
RemoveVirtualMac
|
||||
RemoveVirtualMac,
|
||||
}
|
||||
|
@ -50,6 +50,9 @@ pub struct Settings {
|
||||
#[serde(default)]
|
||||
/// Tax rates to change per country as a percent of the amount
|
||||
pub tax_rate: HashMap<CountryCode, f32>,
|
||||
|
||||
/// public host of lnvps_nostr service
|
||||
pub nostr_address_host: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
@ -237,5 +240,6 @@ pub fn mock_settings() -> Settings {
|
||||
nostr: None,
|
||||
revolut: None,
|
||||
tax_rate: HashMap::from([(CountryCode::IRL, 23.0), (CountryCode::USA, 1.0)]),
|
||||
nostr_address_host: None,
|
||||
}
|
||||
}
|
||||
|
7
lnvps_common/Cargo.toml
Normal file
7
lnvps_common/Cargo.toml
Normal file
@ -0,0 +1,7 @@
|
||||
[package]
|
||||
name = "lnvps_common"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
rocket.workspace = true
|
2
lnvps_common/src/lib.rs
Normal file
2
lnvps_common/src/lib.rs
Normal file
@ -0,0 +1,2 @@
|
||||
pub mod cors;
|
||||
pub use cors::*;
|
@ -6,6 +6,7 @@ edition = "2021"
|
||||
[features]
|
||||
default = ["mysql"]
|
||||
mysql = ["sqlx/mysql"]
|
||||
nostr-domain = []
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
|
25
lnvps_db/migrations/20250402115943_nostr_address.sql
Normal file
25
lnvps_db/migrations/20250402115943_nostr_address.sql
Normal file
@ -0,0 +1,25 @@
|
||||
-- Add migration script here
|
||||
create table nostr_domain
|
||||
(
|
||||
id integer unsigned not null auto_increment primary key,
|
||||
owner_id integer unsigned not null,
|
||||
name varchar(200) not null,
|
||||
enabled bit(1) not null default 0,
|
||||
created timestamp not null default current_timestamp,
|
||||
relays varchar(1024),
|
||||
|
||||
unique key ix_domain_unique (name),
|
||||
constraint fk_nostr_domain_user foreign key (owner_id) references users (id)
|
||||
);
|
||||
create table nostr_domain_handle
|
||||
(
|
||||
id integer unsigned not null auto_increment primary key,
|
||||
domain_id integer unsigned not null,
|
||||
handle varchar(100) not null,
|
||||
created timestamp not null default current_timestamp,
|
||||
pubkey binary(32) not null,
|
||||
relays varchar(1024),
|
||||
|
||||
unique key ix_domain_handle_unique (domain_id, handle),
|
||||
constraint fk_nostr_domain_handle_domain foreign key (domain_id) references nostr_domain (id) on delete cascade
|
||||
)
|
@ -10,7 +10,7 @@ pub use mysql::*;
|
||||
pub use async_trait::async_trait;
|
||||
|
||||
#[async_trait]
|
||||
pub trait LNVpsDb: Sync + Send {
|
||||
pub trait LNVpsDb: LNVPSNostrDb + Send + Sync {
|
||||
/// Migrate database
|
||||
async fn migrate(&self) -> Result<()>;
|
||||
|
||||
@ -173,3 +173,40 @@ pub trait LNVpsDb: Sync + Send {
|
||||
/// Get access policy
|
||||
async fn get_access_policy(&self, access_policy_id: u64) -> Result<AccessPolicy>;
|
||||
}
|
||||
|
||||
#[cfg(feature = "nostr-domain")]
|
||||
#[async_trait]
|
||||
pub trait LNVPSNostrDb: Sync + Send {
|
||||
/// Get single handle for a domain
|
||||
async fn get_handle(&self, handle_id: u64) -> Result<NostrDomainHandle>;
|
||||
|
||||
/// Get single handle for a domain
|
||||
async fn get_handle_by_name(&self, domain_id: u64, handle: &str) -> Result<NostrDomainHandle>;
|
||||
|
||||
/// Insert a new handle
|
||||
async fn insert_handle(&self, handle: &NostrDomainHandle) -> Result<u64>;
|
||||
|
||||
/// Update an existing domain handle
|
||||
async fn update_handle(&self, handle: &NostrDomainHandle) -> Result<()>;
|
||||
|
||||
/// Delete handle entry
|
||||
async fn delete_handle(&self, handle_id: u64) -> Result<()>;
|
||||
|
||||
/// List handles
|
||||
async fn list_handles(&self, domain_id: u64) -> Result<Vec<NostrDomainHandle>>;
|
||||
|
||||
/// Get domain object by id
|
||||
async fn get_domain(&self, id: u64) -> Result<NostrDomain>;
|
||||
|
||||
/// Get domain object by name
|
||||
async fn get_domain_by_name(&self, name: &str) -> Result<NostrDomain>;
|
||||
|
||||
/// List domains owned by a user
|
||||
async fn list_domains(&self, owner_id: u64) -> Result<Vec<NostrDomain>>;
|
||||
|
||||
/// Insert a new domain
|
||||
async fn insert_domain(&self, domain: &NostrDomain) -> Result<u64>;
|
||||
|
||||
/// Delete a domain
|
||||
async fn delete_domain(&self, domain_id: u64) -> Result<()>;
|
||||
}
|
||||
|
@ -489,3 +489,24 @@ impl FromStr for PaymentMethod {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(FromRow, Clone, Debug, Default)]
|
||||
pub struct NostrDomain {
|
||||
pub id: u64,
|
||||
pub owner_id: u64,
|
||||
pub name: String,
|
||||
pub created: DateTime<Utc>,
|
||||
pub enabled: bool,
|
||||
pub relays: Option<String>,
|
||||
pub handles: i64,
|
||||
}
|
||||
|
||||
#[derive(FromRow, Clone, Debug, Default)]
|
||||
pub struct NostrDomainHandle {
|
||||
pub id: u64,
|
||||
pub domain_id: u64,
|
||||
pub handle: String,
|
||||
pub created: DateTime<Utc>,
|
||||
pub pubkey: Vec<u8>,
|
||||
pub relays: Option<String>,
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::{
|
||||
AccessPolicy, IpRange, LNVpsDb, Router, User, UserSshKey, Vm, VmCostPlan, VmCustomPricing,
|
||||
VmCustomPricingDisk, VmCustomTemplate, VmHost, VmHostDisk, VmHostRegion, VmIpAssignment,
|
||||
VmOsImage, VmPayment, VmTemplate,
|
||||
AccessPolicy, IpRange, LNVPSNostrDb, LNVpsDb, NostrDomain, NostrDomainHandle, Router, User,
|
||||
UserSshKey, Vm, VmCostPlan, VmCustomPricing, VmCustomPricingDisk, VmCustomTemplate, VmHost,
|
||||
VmHostDisk, VmHostRegion, VmIpAssignment, VmOsImage, VmPayment, VmTemplate,
|
||||
};
|
||||
use anyhow::{bail, Error, Result};
|
||||
use async_trait::async_trait;
|
||||
@ -556,3 +556,115 @@ impl LNVpsDb for LNVpsDbMysql {
|
||||
.map_err(Error::new)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "nostr-domain")]
|
||||
#[async_trait]
|
||||
impl LNVPSNostrDb for LNVpsDbMysql {
|
||||
async fn get_handle(&self, handle_id: u64) -> Result<NostrDomainHandle> {
|
||||
sqlx::query_as("select * from nostr_domain_handle where id=?")
|
||||
.bind(handle_id)
|
||||
.fetch_one(&self.db)
|
||||
.await
|
||||
.map_err(Error::new)
|
||||
}
|
||||
|
||||
async fn get_handle_by_name(&self, domain_id: u64, handle: &str) -> Result<NostrDomainHandle> {
|
||||
sqlx::query_as("select * from nostr_domain_handle where domain_id=? and handle=?")
|
||||
.bind(domain_id)
|
||||
.bind(handle)
|
||||
.fetch_one(&self.db)
|
||||
.await
|
||||
.map_err(Error::new)
|
||||
}
|
||||
|
||||
async fn insert_handle(&self, handle: &NostrDomainHandle) -> Result<u64> {
|
||||
Ok(
|
||||
sqlx::query(
|
||||
"insert into nostr_domain_handle(domain_id,handle,pubkey,relays) values(?,?,?,?) returning id",
|
||||
)
|
||||
.bind(handle.domain_id)
|
||||
.bind(&handle.handle)
|
||||
.bind(&handle.pubkey)
|
||||
.bind(&handle.relays)
|
||||
.fetch_one(&self.db)
|
||||
.await
|
||||
.map_err(Error::new)?
|
||||
.try_get(0)?,
|
||||
)
|
||||
}
|
||||
|
||||
async fn update_handle(&self, handle: &NostrDomainHandle) -> Result<()> {
|
||||
sqlx::query("update nostr_domain_handle set handle=?,pubkey=?,relays=? where id=?")
|
||||
.bind(&handle.handle)
|
||||
.bind(&handle.pubkey)
|
||||
.bind(&handle.relays)
|
||||
.bind(handle.id)
|
||||
.execute(&self.db)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn delete_handle(&self, handle_id: u64) -> Result<()> {
|
||||
sqlx::query("delete from nostr_domain_handle where id=?")
|
||||
.bind(handle_id)
|
||||
.execute(&self.db)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn list_handles(&self, domain_id: u64) -> Result<Vec<NostrDomainHandle>> {
|
||||
sqlx::query_as("select * from nostr_domain_handle where domain_id=?")
|
||||
.bind(domain_id)
|
||||
.fetch_all(&self.db)
|
||||
.await
|
||||
.map_err(Error::new)
|
||||
}
|
||||
|
||||
async fn get_domain(&self, id: u64) -> Result<NostrDomain> {
|
||||
sqlx::query_as("select *,(select count(1) from nostr_domain_handle where domain_id=nostr_domain.id) handles from nostr_domain where id=?")
|
||||
.bind(id)
|
||||
.fetch_one(&self.db)
|
||||
.await
|
||||
.map_err(Error::new)
|
||||
}
|
||||
|
||||
async fn get_domain_by_name(&self, name: &str) -> Result<NostrDomain> {
|
||||
sqlx::query_as("select *,(select count(1) from nostr_domain_handle where domain_id=nostr_domain.id) handles from nostr_domain where name=?")
|
||||
.bind(name)
|
||||
.fetch_one(&self.db)
|
||||
.await
|
||||
.map_err(Error::new)
|
||||
}
|
||||
|
||||
async fn list_domains(&self, owner_id: u64) -> Result<Vec<NostrDomain>> {
|
||||
sqlx::query_as("select *,(select count(1) from nostr_domain_handle where domain_id=nostr_domain.id) handles from nostr_domain where owner_id=?")
|
||||
.bind(owner_id)
|
||||
.fetch_all(&self.db)
|
||||
.await
|
||||
.map_err(Error::new)
|
||||
}
|
||||
|
||||
async fn insert_domain(&self, domain: &NostrDomain) -> Result<u64> {
|
||||
Ok(
|
||||
sqlx::query(
|
||||
"insert into nostr_domain(owner_id,name,relays) values(?,?,?) returning id",
|
||||
)
|
||||
.bind(domain.owner_id)
|
||||
.bind(&domain.name)
|
||||
.bind(&domain.relays)
|
||||
.fetch_one(&self.db)
|
||||
.await
|
||||
.map_err(Error::new)?
|
||||
.try_get(0)?,
|
||||
)
|
||||
}
|
||||
|
||||
async fn delete_domain(&self, domain_id: u64) -> Result<()> {
|
||||
sqlx::query("update nostr_domain set deleted = current_timestamp where id = ?")
|
||||
.bind(domain_id)
|
||||
.fetch_one(&self.db)
|
||||
.await
|
||||
.map_err(Error::new)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -4,4 +4,14 @@ version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
lnvps_db = { path = "../lnvps_db" }
|
||||
lnvps_db = { path = "../lnvps_db", features = ["nostr-domain"] }
|
||||
lnvps_common = { path = "../lnvps_common" }
|
||||
env_logger.workspace = true
|
||||
log.workspace = true
|
||||
anyhow.workspace = true
|
||||
tokio.workspace = true
|
||||
serde.workspace = true
|
||||
config.workspace = true
|
||||
serde_json.workspace = true
|
||||
rocket.workspace = true
|
||||
hex.workspace = true
|
||||
|
3
lnvps_nostr/README.md
Normal file
3
lnvps_nostr/README.md
Normal file
@ -0,0 +1,3 @@
|
||||
# LNVPS Nostr Services
|
||||
|
||||
A simple webserver hosting various nostr based services for lnvps.net
|
5
lnvps_nostr/config.yaml
Normal file
5
lnvps_nostr/config.yaml
Normal file
@ -0,0 +1,5 @@
|
||||
# Connection string to lnvps database
|
||||
db: "mysql://root:root@localhost:3376/lnvps"
|
||||
|
||||
# Listen address for http server
|
||||
listen: "127.0.0.1:8001"
|
@ -1,3 +1,65 @@
|
||||
fn main() {
|
||||
println!("Hello, world!");
|
||||
mod routes;
|
||||
|
||||
use crate::routes::routes;
|
||||
use anyhow::Result;
|
||||
use config::{Config, File};
|
||||
use lnvps_common::CORS;
|
||||
use lnvps_db::{LNVPSNostrDb, LNVpsDbMysql};
|
||||
use log::error;
|
||||
use rocket::http::Method;
|
||||
use serde::Deserialize;
|
||||
use std::net::{IpAddr, SocketAddr};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Clone, Deserialize)]
|
||||
struct Settings {
|
||||
/// Database connection string
|
||||
db: String,
|
||||
/// Listen address for http server
|
||||
listen: Option<String>,
|
||||
}
|
||||
|
||||
#[rocket::main]
|
||||
async fn main() -> Result<()> {
|
||||
env_logger::init();
|
||||
|
||||
let settings: Settings = Config::builder()
|
||||
.add_source(File::from(PathBuf::from("config.yaml")))
|
||||
.build()?
|
||||
.try_deserialize()?;
|
||||
|
||||
// Connect database
|
||||
let db = LNVpsDbMysql::new(&settings.db).await?;
|
||||
let db: Arc<dyn LNVPSNostrDb> = Arc::new(db);
|
||||
|
||||
let mut config = rocket::Config::default();
|
||||
let ip: SocketAddr = match &settings.listen {
|
||||
Some(i) => i.parse()?,
|
||||
None => SocketAddr::new(IpAddr::from([0, 0, 0, 0]), 8000),
|
||||
};
|
||||
config.address = ip.ip();
|
||||
config.port = ip.port();
|
||||
|
||||
if let Err(e) = rocket::Rocket::custom(config)
|
||||
.manage(db.clone())
|
||||
.manage(settings.clone())
|
||||
.attach(CORS)
|
||||
.mount("/", routes())
|
||||
.mount(
|
||||
"/",
|
||||
vec![rocket::Route::ranked(
|
||||
isize::MAX,
|
||||
Method::Options,
|
||||
"/<catch_all_options_route..>",
|
||||
CORS,
|
||||
)],
|
||||
)
|
||||
.launch()
|
||||
.await
|
||||
{
|
||||
error!("{}", e);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
65
lnvps_nostr/src/routes.rs
Normal file
65
lnvps_nostr/src/routes.rs
Normal file
@ -0,0 +1,65 @@
|
||||
use lnvps_db::LNVPSNostrDb;
|
||||
use log::info;
|
||||
use rocket::request::{FromRequest, Outcome};
|
||||
use rocket::serde::json::Json;
|
||||
use rocket::{Request, Route, State, routes};
|
||||
use serde::Serialize;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub fn routes() -> Vec<Route> {
|
||||
routes![nostr_address]
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct NostrJson {
|
||||
pub names: HashMap<String, String>,
|
||||
pub relays: HashMap<String, Vec<String>>,
|
||||
}
|
||||
|
||||
struct HostInfo<'r> {
|
||||
pub host: Option<&'r str>,
|
||||
}
|
||||
|
||||
#[rocket::async_trait]
|
||||
impl<'r> FromRequest<'r> for HostInfo<'r> {
|
||||
type Error = ();
|
||||
|
||||
async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> {
|
||||
Outcome::Success(HostInfo {
|
||||
host: request.host().map(|h| h.domain().as_str()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[rocket::get("/.well-known/nostr.json?<name>")]
|
||||
async fn nostr_address(
|
||||
host: HostInfo<'_>,
|
||||
db: &State<Arc<dyn LNVPSNostrDb>>,
|
||||
name: Option<&str>,
|
||||
) -> Result<Json<NostrJson>, &'static str> {
|
||||
let name = name.unwrap_or("_");
|
||||
let host = host.host.unwrap_or("lnvps.net");
|
||||
info!("Got request for {} on host {}", name, host);
|
||||
let domain = db
|
||||
.get_domain_by_name(host)
|
||||
.await
|
||||
.map_err(|_| "Domain not found")?;
|
||||
let handle = db
|
||||
.get_handle_by_name(domain.id, name)
|
||||
.await
|
||||
.map_err(|_| "Handle not found")?;
|
||||
|
||||
let pubkey_hex = hex::encode(handle.pubkey);
|
||||
let relays = if let Some(r) = handle.relays {
|
||||
r.split(",").map(|x| x.to_string()).collect()
|
||||
} else if let Some(r) = domain.relays {
|
||||
r.split(",").map(|x| x.to_string()).collect()
|
||||
} else {
|
||||
vec![]
|
||||
};
|
||||
Ok(Json(NostrJson {
|
||||
names: HashMap::from([(name.to_string(), pubkey_hex.clone())]),
|
||||
relays: HashMap::from([(pubkey_hex, relays)]),
|
||||
}))
|
||||
}
|
Reference in New Issue
Block a user