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:
@ -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