Serve vendor files (htmx) from MinIO instead of local filesystem
- Added serve_vendor_file() to serve from {bot}.gblib/vendor/ in MinIO
- Added /js/vendor/* route to app_server
- Removed local ServeDir for /js/vendor from main.rs
- Added ensure_vendor_files_in_minio() to upload htmx.min.js on startup
- Uses include_bytes! to embed htmx.min.js in binary
This commit is contained in:
parent
bbbb9e190f
commit
a43aea3320
2 changed files with 88 additions and 2 deletions
|
|
@ -13,6 +13,69 @@ use std::sync::Arc;
|
|||
|
||||
/// Rewrite CDN URLs to local paths for HTMX and other vendor libraries
|
||||
/// This ensures old apps with CDN references still work with local files
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
pub struct VendorFilePath {
|
||||
pub file_path: String,
|
||||
}
|
||||
|
||||
pub async fn serve_vendor_file(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Path(params): Path<VendorFilePath>,
|
||||
) -> Response {
|
||||
let file_path = sanitize_file_path(¶ms.file_path);
|
||||
|
||||
if file_path.is_empty() {
|
||||
return (StatusCode::BAD_REQUEST, "Invalid path").into_response();
|
||||
}
|
||||
|
||||
let bot_name = state.bucket_name
|
||||
.trim_end_matches(".gbai")
|
||||
.to_string();
|
||||
let sanitized_bot_name = bot_name.to_lowercase().replace(' ', "-").replace('_', "-");
|
||||
|
||||
let bucket = format!("{}.gbai", sanitized_bot_name);
|
||||
let key = format!("{}.gblib/vendor/{}", sanitized_bot_name, file_path);
|
||||
|
||||
info!("Serving vendor file from MinIO: bucket={}, key={}", bucket, key);
|
||||
|
||||
if let Some(ref drive) = state.drive {
|
||||
match drive
|
||||
.get_object()
|
||||
.bucket(&bucket)
|
||||
.key(&key)
|
||||
.send()
|
||||
.await
|
||||
{
|
||||
Ok(response) => {
|
||||
match response.body.collect().await {
|
||||
Ok(body) => {
|
||||
let content = body.into_bytes();
|
||||
let content_type = get_content_type(&file_path);
|
||||
|
||||
return Response::builder()
|
||||
.status(StatusCode::OK)
|
||||
.header(header::CONTENT_TYPE, content_type)
|
||||
.header(header::CACHE_CONTROL, "public, max-age=86400")
|
||||
.body(Body::from(content.to_vec()))
|
||||
.unwrap_or_else(|_| {
|
||||
(StatusCode::INTERNAL_SERVER_ERROR, "Failed to build response")
|
||||
.into_response()
|
||||
});
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to read MinIO response body: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("MinIO get_object failed for {}/{}: {}", bucket, key, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(StatusCode::NOT_FOUND, "Vendor file not found").into_response()
|
||||
}
|
||||
|
||||
fn rewrite_cdn_urls(html: &str) -> String {
|
||||
html
|
||||
// HTMX from various CDNs
|
||||
|
|
@ -31,6 +94,8 @@ fn rewrite_cdn_urls(html: &str) -> String {
|
|||
|
||||
pub fn configure_app_server_routes() -> Router<Arc<AppState>> {
|
||||
Router::new()
|
||||
// Serve shared vendor files from MinIO: /js/vendor/*
|
||||
.route("/js/vendor/*file_path", get(serve_vendor_file))
|
||||
// Serve app files: /apps/{app_name}/* (clean URLs)
|
||||
.route("/apps/:app_name", get(serve_app_index))
|
||||
.route("/apps/:app_name/", get(serve_app_index))
|
||||
|
|
|
|||
25
src/main.rs
25
src/main.rs
|
|
@ -23,6 +23,27 @@ use std::sync::Arc;
|
|||
use tower_http::services::ServeDir;
|
||||
use tower_http::trace::TraceLayer;
|
||||
|
||||
async fn ensure_vendor_files_in_minio(drive: &aws_sdk_s3::Client) {
|
||||
use aws_sdk_s3::primitives::ByteStream;
|
||||
|
||||
let htmx_content = include_bytes!("../../botserver-stack/static/js/vendor/htmx.min.js");
|
||||
let bucket = "default.gbai";
|
||||
let key = "default.gblib/vendor/htmx.min.js";
|
||||
|
||||
match drive
|
||||
.put_object()
|
||||
.bucket(bucket)
|
||||
.key(key)
|
||||
.body(ByteStream::from_static(htmx_content))
|
||||
.content_type("application/javascript")
|
||||
.send()
|
||||
.await
|
||||
{
|
||||
Ok(_) => info!("Uploaded vendor file to MinIO: s3://{}/{}", bucket, key),
|
||||
Err(e) => warn!("Failed to upload vendor file to MinIO: {}", e),
|
||||
}
|
||||
}
|
||||
|
||||
use botserver::security::{
|
||||
auth_middleware, create_cors_layer, create_rate_limit_layer, create_security_headers_layer,
|
||||
request_id_middleware, security_headers_middleware, set_cors_allowed_origins,
|
||||
|
|
@ -314,8 +335,6 @@ async fn run_axum_server(
|
|||
auth_config.clone(),
|
||||
auth_middleware,
|
||||
))
|
||||
// Vendor JS files (htmx, etc.) served locally - no CDN
|
||||
.nest_service("/js/vendor", ServeDir::new("./botserver-stack/static/js/vendor"))
|
||||
// Static files fallback for legacy /apps/* paths
|
||||
.nest_service("/static", ServeDir::new(&site_path))
|
||||
// Security middleware stack (order matters - first added is outermost)
|
||||
|
|
@ -730,6 +749,8 @@ async fn main() -> std::io::Result<()> {
|
|||
.await
|
||||
.map_err(|e| std::io::Error::other(format!("Failed to initialize Drive: {}", e)))?;
|
||||
|
||||
ensure_vendor_files_in_minio(&drive).await;
|
||||
|
||||
let session_manager = Arc::new(tokio::sync::Mutex::new(session::SessionManager::new(
|
||||
pool.get().map_err(|e| std::io::Error::other(format!("Failed to get database connection: {}", e)))?,
|
||||
redis_client.clone(),
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue