From a03570baf2f29639db7663fb1c1e73e23a2c1334 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ioan=20Biz=C4=83u?= Date: Fri, 31 May 2024 00:39:03 +0300 Subject: [PATCH] Make admin interface work on localhost and http. --- README.md | 8 ++----- admin/index.html | 32 ++++++++++++++++++------- src/main.rs | 61 ++++++++++++++++++------------------------------ 3 files changed, 49 insertions(+), 52 deletions(-) diff --git a/README.md b/README.md index 9533246..b377417 100644 --- a/README.md +++ b/README.md @@ -193,13 +193,11 @@ Ways you can post to your site: A simple REST API exists that can be used to create new sites and list sites associated with a Nostr pubkey. -In order to activate the API, you need to pass `--admin-domain `. Servus will listen to that domain for API requests. - ### `/api/sites` -A `POST` to `https:///api/sites` can be used to add a new site associated with a key. +A `POST` to `/api/sites` can be used to add a new site associated with a key. -A `GET` to `https:///api/sites` can be used to get a list of all the sites belonging to a key. +A `GET` to `/api/sites` can be used to get a list of all the sites belonging to a key. NB: Both requests require a [NIP-98](https://github.com/nostr-protocol/nips/blob/master/98.md) authorization header to be present, which will be validated and used to decide which Nostr pubkey the request is referring to! @@ -209,8 +207,6 @@ Servus also implements the [Blossom API](https://github.com/hzrd149/blossom) and ## Admin interface -The same `--admin-domain ` flag used to activate the REST API is also used to activate... you guessed it... the *admin interface*! - The *admin interface* requires you to have a Nostr extension such as [Alby](https://getalby.com/) or [nos2x](https://github.com/fiatjaf/nos2x) installed in your browser and lets you create sites, create posts and edit posts. Still very experimental, even more so than **Servus** itself! ## Any questions? diff --git a/admin/index.html b/admin/index.html index 78a343b..bb505ac 100644 --- a/admin/index.html +++ b/admin/index.html @@ -56,10 +56,26 @@ }); } + function getWebSocketUrl(siteDomain) { + if (API_BASE_URL.startsWith("//localhost:")) { + return `ws:${API_BASE_URL}`; + } else { + return `${WS_PROTOCOL}//${site.domain}`; + } + } + + function getBlossomBaseUrl(siteDomain) { + if (API_BASE_URL.startsWith("//localhost:")) { + return `http:${API_BASE_URL}`; + } else { + return `${window.location.protocol}//${siteDomain}`; + } + } + function getPosts(sites, posts) { posts.length = 0; for (let site of sites) { - let ws = new WebSocket(`${WS_PROTOCOL}//${site.domain}`); + let ws = new WebSocket(getWebSocketUrl(site.domain)); ws.onmessage = (e) => { let r = JSON.parse(e.data); if (r[0] === 'EVENT') { @@ -94,7 +110,7 @@ } function savePost(post, kind, published_at) { - let ws = new WebSocket(`${WS_PROTOCOL}//${post.site.domain}`); + let ws = new WebSocket(getWebSocketUrl(post.site.domain)); ws.onopen = async (e) => { if (post.id === undefined) { post.id = post.title.toLowerCase().replace(/ /g, "-").replace(/[^\w-]+/g, ""); @@ -109,7 +125,7 @@ } function deleteResource(post, event_id) { - let ws = new WebSocket(`${WS_PROTOCOL}//${post.site.domain}`); + let ws = new WebSocket(getWebSocketUrl(post.site.domain)); ws.onopen = async (e) => { ws.send(JSON.stringify(['EVENT', await getEvent(5, "", [['e', event_id]])])); post.persisted = false; @@ -119,7 +135,7 @@ function getNotes(site, notes) { notes.length = 0; - let ws = new WebSocket(`${WS_PROTOCOL}//${site.domain}`); + let ws = new WebSocket(getWebSocketUrl(site.domain)); ws.onmessage = (e) => { let r = JSON.parse(e.data); if (r[0] === 'EVENT') { @@ -136,21 +152,21 @@ async function getFiles(site, files) { files.length = 0; - const res = await fetch(new URL(`${window.location.protocol}//${site.domain}/list/${await window.nostr.getPublicKey()}`)); + const res = await fetch(new URL(`${getBlossomBaseUrl(site.domain)}/list/${await window.nostr.getPublicKey()}`)); for (f of await res.json()) { files.push(f); } } function saveNote(note) { - let ws = new WebSocket(`${WS_PROTOCOL}//${note.site.domain}`); + let ws = new WebSocket(getWebSocketUrl(note.site.domain)); ws.onopen = async (e) => { ws.send(JSON.stringify(['EVENT', await getEvent(EVENT_KIND_NOTE, note.content, [])])); }; } async function uploadFileBlossom(site) { - const endpoint = `${window.location.protocol}//${site.domain}/upload`; + const endpoint = `${getBlossomBaseUrl(site.domain)}/upload`; let fileInput = document.querySelector('#fileInput'); const res = await fetch(new URL(endpoint), { method: "PUT", @@ -161,7 +177,7 @@ } async function deleteFile(site, sha256) { - let endpoint = `${window.location.protocol}//${site.domain}/${sha256}`; + let endpoint = `${getBlossomBaseUrl(site.domain)}/${sha256}`; await fetch(new URL(endpoint), { method: 'DELETE', diff --git a/src/main.rs b/src/main.rs index 0775e71..e6d66b5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -29,9 +29,6 @@ use site::Site; #[derive(Parser)] struct Cli { - #[clap(short('a'), long)] - admin_domain: Option, - #[clap(short('e'), long)] contact_email: Option, @@ -53,7 +50,6 @@ struct Cli { #[derive(Clone)] struct State { - admin_domain: Option, sites: Arc>>, } @@ -234,20 +230,6 @@ async fn handle_websocket( } async fn handle_index(request: Request) -> tide::Result { - let state = &request.state(); - - if state.admin_domain.is_some() { - let admin_domain = state.admin_domain.to_owned().unwrap(); - if *request.host().unwrap() == admin_domain { - let admin_index = - admin::INDEX_HTML.replace("%%API_BASE_URL%%", &format!("//{}", admin_domain)); - return Ok(Response::builder(StatusCode::Ok) - .content_type(mime::HTML) - .body(admin_index) - .build()); - } - } - if let Some(site) = get_site(&request) { let resources = site.resources.read().unwrap(); match resources.get("/index") { @@ -280,6 +262,17 @@ async fn handle_request(request: Request) -> tide::Result { path = path.strip_suffix('/').unwrap(); } + if path == ".admin" { + let admin_index = admin::INDEX_HTML.replace( + "%%API_BASE_URL%%", + &format!("//{}", request.host().unwrap()), + ); + return Ok(Response::builder(StatusCode::Ok) + .content_type(mime::HTML) + .body(admin_index) + .build()); + } + let mut part: Option = None; if path.contains(".") { let parts = path.split(".").collect::>(); @@ -401,16 +394,6 @@ async fn handle_post_site(mut request: Request) -> tide::Result .domain; let state = &request.state(); - if state.admin_domain.is_none() { - return Ok(Response::builder(StatusCode::NotFound).build()); - } - - let admin_domain = state.admin_domain.to_owned().unwrap(); - - if *request.host().unwrap() != admin_domain { - return Ok(Response::builder(StatusCode::NotFound).build()); - } - if state.sites.read().unwrap().contains_key(&domain) { Ok(Response::builder(StatusCode::Conflict).build()) } else { @@ -661,8 +644,9 @@ async fn main() -> Result<(), std::io::Error> { sites = existing_sites; } + let site_count = sites.len(); + let mut app = tide::with_state(State { - admin_domain: args.admin_domain.clone(), sites: Arc::new(RwLock::new(sites)), }); @@ -676,11 +660,9 @@ async fn main() -> Result<(), std::io::Error> { .put(handle_upload_request); app.at("/list/:pubkey").get(handle_list_request); app.at("/:sha256").delete(handle_delete_request); - if args.admin_domain.is_some() { - app.at("/api/sites") - .post(handle_post_site) - .get(handle_get_sites); - } + app.at("/api/sites") + .post(handle_post_site) + .get(handle_get_sites); let addr = "0.0.0.0"; @@ -696,7 +678,7 @@ async fn main() -> Result<(), std::io::Error> { if args.contact_email.is_none() { panic!("Use -e to provide a contact email!"); } - let mut domains: Vec = app + let domains: Vec = app .state() .sites .read() @@ -704,9 +686,6 @@ async fn main() -> Result<(), std::io::Error> { .keys() .map(|x| x.to_string()) .collect(); - if args.admin_domain.is_some() { - domains.push(args.admin_domain.unwrap()); - } let cache = DirCache::new("./cache"); let acme_config = AcmeConfig::new(domains) .cache(cache) @@ -723,6 +702,12 @@ async fn main() -> Result<(), std::io::Error> { } else { let port = args.port.unwrap_or(4884); let bind_to = format!("{addr}:{port}"); + println!("####################################"); + if site_count == 1 { + println!("*** Your site: http://localhost:{port}/ ***"); + } + println!("*** The admin interface: http://localhost:{port}/.admin/ ***"); + println!("####################################"); app.listen(bind_to).await?; };