server: multiple frontends + auto download from github when needed

This commit is contained in:
nym21
2025-03-05 12:22:11 +01:00
parent 0d0edd7917
commit b27297cdc6
29 changed files with 892 additions and 99 deletions
+36 -21
View File
@@ -1,21 +1,34 @@
use std::{fs, io};
use std::{fs, io, path::Path};
use brk_query::{Index, Query};
use crate::WEBSITE_DEV_PATH;
use crate::Frontend;
const SCRIPTS: &str = "scripts";
const TPYES: &str = "types";
#[allow(clippy::upper_case_acronyms)]
pub trait DTS {
fn generate_dts_file(&self) -> io::Result<()>;
fn generate_dts_file(&self, frontend: Frontend, websites_path: &Path) -> io::Result<()>;
}
impl DTS for Query<'static> {
fn generate_dts_file(&self) -> io::Result<()> {
if !fs::exists(WEBSITE_DEV_PATH)? {
fn generate_dts_file(&self, frontend: Frontend, websites_path: &Path) -> io::Result<()> {
if frontend.is_none() {
return Ok(());
}
let path = format!("{WEBSITE_DEV_PATH}/scripts/types/vecid-to-indexes.d.ts");
let path = websites_path.join(frontend.to_folder_name());
if !fs::exists(&path)? {
return Ok(());
}
let path = path.join(SCRIPTS).join(TPYES);
fs::create_dir_all(&path)?;
let path = path.join(Path::new("vecid-to-indexes.d.ts"));
let mut contents = Index::all()
.into_iter()
@@ -26,22 +39,24 @@ impl DTS for Query<'static> {
contents += "\n\ninterface VecIdToIndexes {\n";
self.vecid_to_index_to_vec.iter().for_each(|(id, index_to_vec)| {
let indexes = index_to_vec
.keys()
.map(|i| i.to_string())
.collect::<Vec<_>>()
.join(", ");
self.vecid_to_index_to_vec
.iter()
.for_each(|(id, index_to_vec)| {
let indexes = index_to_vec
.keys()
.map(|i| i.to_string())
.collect::<Vec<_>>()
.join(", ");
contents += &format!(
" {}: [{indexes}]\n",
if id.contains("-") {
format!("\"{id}\"")
} else {
id.to_owned()
}
);
});
contents += &format!(
" {}: [{indexes}]\n",
if id.contains("-") {
format!("\"{id}\"")
} else {
id.to_owned()
}
);
});
contents.push('}');
+36 -19
View File
@@ -1,56 +1,73 @@
use std::{
fs::{self},
path::{Path, PathBuf},
path::Path,
time::Instant,
};
use axum::{
body::Body,
extract,
extract::{self, State},
http::{HeaderMap, StatusCode},
response::{IntoResponse, Response},
};
use log::{error, info};
use crate::{
WEBSITE_DEV_PATH, log_result,
AppState, log_result,
traits::{HeaderMapExtended, ModifiedState, ResponseExtended},
};
use super::minify::minify_js;
pub async fn file_handler(headers: HeaderMap, path: extract::Path<String>) -> Response {
any_handler(headers, Some(path))
pub async fn file_handler(
headers: HeaderMap,
State(app_state): State<AppState>,
path: extract::Path<String>,
) -> Response {
any_handler(headers, app_state, Some(path))
}
pub async fn index_handler(headers: HeaderMap) -> Response {
any_handler(headers, None)
pub async fn index_handler(headers: HeaderMap, State(app_state): State<AppState>) -> Response {
any_handler(headers, app_state, None)
}
fn any_handler(headers: HeaderMap, path: Option<extract::Path<String>>) -> Response {
fn any_handler(
headers: HeaderMap,
app_state: AppState,
path: Option<extract::Path<String>>,
) -> Response {
let website_path = app_state
.websites_path
.as_ref()
.expect("Should never reach here is websites_path is None")
.join(app_state.frontend.to_folder_name());
let instant = Instant::now();
let response = if let Some(path) = path.as_ref() {
let path = path.0.replace("..", "").replace("\\", "");
let mut path = str_to_path(&path);
let mut path = website_path.join(&path);
if !path.exists() {
if path.extension().is_some() {
let mut response: Response<Body> =
(StatusCode::INTERNAL_SERVER_ERROR, "File doesn't exist".to_string()).into_response();
let mut response: Response<Body> = (
StatusCode::INTERNAL_SERVER_ERROR,
"File doesn't exist".to_string(),
)
.into_response();
response.headers_mut().insert_cors();
return response;
} else {
path = str_to_path("index.html");
path = website_path.join("index.html");
}
}
path_to_response(&headers, &path)
} else {
path_to_response(&headers, &str_to_path("index.html"))
path_to_response(&headers, &website_path.join("index.html"))
};
log_result(
@@ -66,7 +83,8 @@ fn path_to_response(headers: &HeaderMap, path: &Path) -> Response {
match path_to_response_(headers, path) {
Ok(response) => response,
Err(error) => {
let mut response = (StatusCode::INTERNAL_SERVER_ERROR, error.to_string()).into_response();
let mut response =
(StatusCode::INTERNAL_SERVER_ERROR, error.to_string()).into_response();
response.headers_mut().insert_cors();
@@ -116,7 +134,10 @@ fn path_to_response_(headers: &HeaderMap, path: &Path) -> color_eyre::Result<Res
|| serialized_path.contains("assets/")
|| serialized_path.contains("packages/")
|| path.extension().is_some_and(|extension| {
extension == "pdf" || extension == "jpg" || extension == "png" || extension == "woff2"
extension == "pdf"
|| extension == "jpg"
|| extension == "png"
|| extension == "woff2"
})
{
headers.insert_cache_control_immutable();
@@ -127,7 +148,3 @@ fn path_to_response_(headers: &HeaderMap, path: &Path) -> color_eyre::Result<Res
Ok(response)
}
fn str_to_path(path: &str) -> PathBuf {
PathBuf::from(&format!("{WEBSITE_DEV_PATH}{path}"))
}
+30
View File
@@ -0,0 +1,30 @@
use clap::ValueEnum;
use serde::{Deserialize, Serialize};
#[derive(
Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize, ValueEnum,
)]
pub enum Frontend {
None,
#[default]
KiboMoney,
Custom,
}
impl Frontend {
pub fn is_none(&self) -> bool {
self == &Self::None
}
pub fn is_some(&self) -> bool {
!self.is_none()
}
pub fn to_folder_name(&self) -> &str {
match self {
Self::Custom => "custom",
Self::KiboMoney => "kibo.money",
Self::None => unreachable!(),
}
}
}
+11 -4
View File
@@ -1,18 +1,25 @@
use axum::{routing::get, Router};
use axum::{Router, routing::get};
use super::AppState;
mod file;
mod frontend;
mod minify;
use file::{file_handler, index_handler};
pub use frontend::Frontend;
pub trait FilesRoutes {
fn add_website_routes(self) -> Self;
fn add_website_routes(self, frontend: Frontend) -> Self;
}
impl FilesRoutes for Router<AppState> {
fn add_website_routes(self) -> Self {
self.route("/{*path}", get(file_handler)).route("/", get(index_handler))
fn add_website_routes(self, frontend: Frontend) -> Self {
if frontend.is_some() {
self.route("/{*path}", get(file_handler))
.route("/", get(index_handler))
} else {
self
}
}
}
+60 -7
View File
@@ -1,14 +1,19 @@
#![cfg_attr(docsrs, feature(doc_cfg))]
#![doc = include_str!("../README.md")]
#![doc = "\n## Example\n\n```rust"]
#![doc = include_str!("../examples/main.rs")]
#![doc = "```"]
use std::time::Instant;
use std::{
fs,
io::Cursor,
path::{Path, PathBuf},
time::Instant,
};
use api::{ApiRoutes, DTS};
use axum::{Json, Router, http::StatusCode, routing::get, serve};
use brk_computer::Computer;
use brk_core::path_dot_brk;
use brk_indexer::Indexer;
use brk_query::Query;
use color_eyre::owo_colors::OwoColorize;
@@ -22,21 +27,69 @@ mod api;
mod files;
mod traits;
pub use files::Frontend;
#[derive(Clone)]
pub struct AppState {
query: &'static Query<'static>,
frontend: Frontend,
websites_path: Option<PathBuf>,
}
pub const WEBSITE_DEV_PATH: &str = "../../websites/kibo.money/";
const DEV_PATH: &str = "../..";
const DOWNLOADS: &str = "downloads";
const WEBSITES: &str = "websites";
pub async fn main(indexer: Indexer, computer: Computer) -> color_eyre::Result<()> {
pub async fn main(
indexer: Indexer,
computer: Computer,
frontend: Frontend,
) -> color_eyre::Result<()> {
let indexer = Box::leak(Box::new(indexer));
let computer = Box::leak(Box::new(computer));
let query = Box::leak(Box::new(Query::build(indexer, computer)));
query.generate_dts_file()?;
let websites_path = if frontend.is_some() {
let websites_dev_path = Path::new(DEV_PATH).join(WEBSITES);
let state = AppState { query };
let websites_path = if fs::exists(&websites_dev_path)? {
websites_dev_path
} else {
let downloads_path = path_dot_brk().join(DOWNLOADS);
let downloaded_websites_path = downloads_path.join("brk-main").join(WEBSITES);
if !fs::exists(&downloaded_websites_path)? {
info!("Downloading websites from Github...");
// TODO
// Need to download versioned, this is only for testing !
let url = "https://github.com/bitcoinresearchkit/brk/archive/refs/heads/main.zip";
let response = minreq::get(url).send()?;
let bytes = response.as_bytes();
let cursor = Cursor::new(bytes);
let mut zip = zip::ZipArchive::new(cursor)?;
zip.extract(&downloads_path)?;
}
downloaded_websites_path
};
query.generate_dts_file(frontend, websites_path.as_path())?;
Some(websites_path)
} else {
None
};
let state = AppState {
query,
frontend,
websites_path,
};
let compression_layer = CompressionLayer::new()
.br(true)
@@ -46,7 +99,7 @@ pub async fn main(indexer: Indexer, computer: Computer) -> color_eyre::Result<()
let router = Router::new()
.add_api_routes()
.add_website_routes()
.add_website_routes(frontend)
.route("/version", get(Json(env!("CARGO_PKG_VERSION"))))
.with_state(state)
.layer(compression_layer);