1use async_graphql::http::{playground_source, GraphQLPlaygroundConfig};
6use async_graphql_axum::{GraphQLRequest, GraphQLResponse};
7use axum::{
8 body::Body,
9 extract::{Multipart, Path, State},
10 http::{header, HeaderMap, StatusCode},
11 response::{Html, IntoResponse, Response},
12 routing::{get, post},
13 Json, Router,
14};
15use ipfrs_core::{Cid, Result as CoreResult};
16use ipfrs_semantic::{DistanceMetric, QueryFilter, RouterConfig, SemanticRouter};
17use ipfrs_storage::{BlockStoreConfig, BlockStoreTrait, SledBlockStore};
18use ipfrs_tensorlogic::{Predicate, Proof, Rule, Substitution, TensorLogicStore, Term};
19use serde::{Deserialize, Serialize};
20use std::sync::Arc;
21use tower_http::compression::CompressionLayer;
22use tower_http::trace::TraceLayer;
23use tracing::{error, info};
24
25use crate::middleware::{
26 add_caching_headers, check_etag_match, not_modified_response, CacheConfig,
27};
28
29use crate::auth::AuthState;
30use crate::auth_handlers;
31use crate::graphql::{create_schema, IpfrsSchema};
32use crate::streaming;
33use crate::tensor;
34use crate::tls::TlsConfig;
35
36#[derive(Clone)]
38pub struct GatewayState {
39 pub(crate) store: Arc<SledBlockStore>,
40 semantic: Option<Arc<SemanticRouter>>,
41 tensorlogic: Option<Arc<TensorLogicStore<SledBlockStore>>>,
42 network: Option<Arc<tokio::sync::Mutex<ipfrs_network::NetworkNode>>>,
43 graphql_schema: Option<IpfrsSchema>,
44 pub(crate) auth: Option<AuthState>,
45}
46
47impl GatewayState {
48 pub fn new(config: BlockStoreConfig) -> CoreResult<Self> {
50 let store = SledBlockStore::new(config)?;
51 Ok(Self {
52 store: Arc::new(store),
53 semantic: None,
54 tensorlogic: None,
55 network: None,
56 graphql_schema: None,
57 auth: None,
58 })
59 }
60
61 pub fn with_auth(
63 mut self,
64 secret: &[u8],
65 default_admin_password: Option<&str>,
66 ) -> CoreResult<Self> {
67 let auth_state = if let Some(password) = default_admin_password {
68 AuthState::with_default_admin(secret, password).map_err(|e| {
69 ipfrs_core::Error::Internal(format!("Failed to create auth state: {}", e))
70 })?
71 } else {
72 AuthState::new(secret)
73 };
74 self.auth = Some(auth_state);
75 Ok(self)
76 }
77
78 pub fn with_semantic(mut self, config: RouterConfig) -> CoreResult<Self> {
80 let semantic = SemanticRouter::new(config).map_err(|e| {
81 ipfrs_core::Error::Internal(format!("Failed to create semantic router: {}", e))
82 })?;
83 self.semantic = Some(Arc::new(semantic));
84 Ok(self)
85 }
86
87 pub fn with_tensorlogic(mut self) -> CoreResult<Self> {
89 let tensorlogic = TensorLogicStore::new(Arc::clone(&self.store))?;
90 self.tensorlogic = Some(Arc::new(tensorlogic));
91 Ok(self)
92 }
93
94 pub fn with_network(mut self, network: ipfrs_network::NetworkNode) -> Self {
96 self.network = Some(Arc::new(tokio::sync::Mutex::new(network)));
97 self
98 }
99
100 pub fn with_graphql(mut self) -> Self {
102 let schema = create_schema(
103 Arc::clone(&self.store),
104 self.semantic.clone(),
105 self.tensorlogic.clone(),
106 );
107 self.graphql_schema = Some(schema);
108 self
109 }
110}
111
112#[derive(Debug, Clone)]
114pub struct GatewayConfig {
115 pub listen_addr: String,
117 pub storage_config: BlockStoreConfig,
119 pub tls_config: Option<TlsConfig>,
121 pub compression_config: crate::middleware::CompressionConfig,
123}
124
125impl Default for GatewayConfig {
126 fn default() -> Self {
127 Self {
128 listen_addr: "127.0.0.1:8080".to_string(),
129 storage_config: BlockStoreConfig::default(),
130 tls_config: None,
131 compression_config: crate::middleware::CompressionConfig::default(),
132 }
133 }
134}
135
136impl GatewayConfig {
137 pub fn production() -> Self {
145 Self {
146 listen_addr: "0.0.0.0:8080".to_string(),
147 storage_config: BlockStoreConfig::default()
148 .with_path("./ipfrs_data".into())
149 .with_cache_mb(500),
150 tls_config: None,
151 compression_config: crate::middleware::CompressionConfig {
152 enable_gzip: true,
153 enable_brotli: true,
154 enable_deflate: true,
155 level: crate::middleware::CompressionLevel::Best,
156 min_size: 512,
157 },
158 }
159 }
160
161 pub fn development() -> Self {
169 Self {
170 listen_addr: "127.0.0.1:8080".to_string(),
171 storage_config: BlockStoreConfig::default()
172 .with_path("./dev_data".into())
173 .with_cache_mb(50),
174 tls_config: None,
175 compression_config: crate::middleware::CompressionConfig {
176 enable_gzip: true,
177 enable_brotli: false,
178 enable_deflate: false,
179 level: crate::middleware::CompressionLevel::Fastest,
180 min_size: 2048,
181 },
182 }
183 }
184
185 pub fn testing() -> Self {
193 Self {
194 listen_addr: "127.0.0.1:0".to_string(),
195 storage_config: BlockStoreConfig::default()
196 .with_path("/tmp/ipfrs_test".into())
197 .with_cache_mb(10),
198 tls_config: None,
199 compression_config: crate::middleware::CompressionConfig {
200 enable_gzip: false,
201 enable_brotli: false,
202 enable_deflate: false,
203 level: crate::middleware::CompressionLevel::Fastest,
204 min_size: 1048576, },
206 }
207 }
208
209 pub fn with_listen_addr(mut self, addr: impl Into<String>) -> Self {
211 self.listen_addr = addr.into();
212 self
213 }
214
215 pub fn with_storage_path(mut self, path: impl Into<String>) -> Self {
217 self.storage_config = self.storage_config.with_path(path.into().into());
218 self
219 }
220
221 pub fn with_cache_mb(mut self, size_mb: usize) -> Self {
223 self.storage_config = self.storage_config.with_cache_mb(size_mb);
224 self
225 }
226
227 pub fn with_tls(mut self, tls_config: TlsConfig) -> Self {
229 self.tls_config = Some(tls_config);
230 self
231 }
232
233 pub fn with_compression_level(mut self, level: crate::middleware::CompressionLevel) -> Self {
235 self.compression_config.level = level;
236 self
237 }
238
239 pub fn with_full_compression(mut self) -> Self {
241 self.compression_config.enable_gzip = true;
242 self.compression_config.enable_brotli = true;
243 self.compression_config.enable_deflate = true;
244 self
245 }
246
247 pub fn without_compression(mut self) -> Self {
249 self.compression_config.enable_gzip = false;
250 self.compression_config.enable_brotli = false;
251 self.compression_config.enable_deflate = false;
252 self
253 }
254
255 pub fn validate(&self) -> CoreResult<()> {
259 if self.listen_addr.is_empty() {
261 return Err(ipfrs_core::Error::Internal(
262 "Listen address cannot be empty".to_string(),
263 ));
264 }
265
266 self.listen_addr
268 .parse::<std::net::SocketAddr>()
269 .map_err(|e| ipfrs_core::Error::Internal(format!("Invalid listen address: {}", e)))?;
270
271 if self.storage_config.path.as_os_str().is_empty() {
273 return Err(ipfrs_core::Error::Internal(
274 "Storage path cannot be empty".to_string(),
275 ));
276 }
277
278 if self.compression_config.min_size == 0 {
280 return Err(ipfrs_core::Error::Internal(
281 "Compression min_size must be greater than 0".to_string(),
282 ));
283 }
284
285 Ok(())
286 }
287}
288
289pub struct Gateway {
291 config: GatewayConfig,
292 state: GatewayState,
293}
294
295impl Gateway {
296 pub fn new(config: GatewayConfig) -> CoreResult<Self> {
298 let state = GatewayState::new(config.storage_config.clone())?;
299 Ok(Self { config, state })
300 }
301
302 fn router(&self) -> Router {
304 let mut router = Router::new()
305 .route("/health", get(health_check))
307 .route("/metrics", get(metrics_endpoint))
309 .route("/ipfs/:cid", get(get_content))
311 .route("/api/v0/auth/login", post(auth_handlers::login_handler))
313 .route(
314 "/api/v0/auth/register",
315 post(auth_handlers::register_handler),
316 )
317 .route("/graphql", post(graphql_handler))
319 .route("/graphql", get(graphql_playground))
320 .route("/api/v0/version", get(api_version))
322 .route("/api/v0/add", post(api_add))
323 .route("/api/v0/block/get", post(api_block_get))
324 .route("/api/v0/block/put", post(api_block_put))
325 .route("/api/v0/block/stat", post(api_block_stat))
326 .route("/api/v0/cat", post(api_cat))
327 .route("/api/v0/dag/get", post(api_dag_get))
328 .route("/api/v0/dag/put", post(api_dag_put))
329 .route("/api/v0/dag/resolve", post(api_dag_resolve))
330 .route("/api/v0/semantic/index", post(api_semantic_index))
332 .route("/api/v0/semantic/search", post(api_semantic_search))
333 .route("/api/v0/semantic/stats", get(api_semantic_stats))
334 .route("/api/v0/semantic/save", post(api_semantic_save))
335 .route("/api/v0/semantic/load", post(api_semantic_load))
336 .route("/api/v0/logic/term", post(api_logic_store_term))
338 .route("/api/v0/logic/term/:cid", get(api_logic_get_term))
339 .route("/api/v0/logic/predicate", post(api_logic_store_predicate))
340 .route("/api/v0/logic/rule", post(api_logic_store_rule))
341 .route("/api/v0/logic/stats", get(api_logic_stats))
342 .route("/api/v0/logic/fact", post(api_logic_add_fact))
343 .route("/api/v0/logic/rule/add", post(api_logic_add_rule))
344 .route("/api/v0/logic/infer", post(api_logic_infer))
345 .route("/api/v0/logic/prove", post(api_logic_prove))
346 .route("/api/v0/logic/verify", post(api_logic_verify))
347 .route("/api/v0/logic/proof/:cid", get(api_logic_get_proof))
348 .route("/api/v0/logic/kb/stats", get(api_logic_kb_stats))
349 .route("/api/v0/logic/kb/save", post(api_logic_kb_save))
350 .route("/api/v0/logic/kb/load", post(api_logic_kb_load))
351 .route("/api/v0/id", get(api_network_id))
353 .route("/api/v0/swarm/peers", get(api_swarm_peers))
354 .route("/api/v0/swarm/connect", post(api_swarm_connect))
355 .route("/api/v0/swarm/disconnect", post(api_swarm_disconnect))
356 .route("/api/v0/dht/findprovs", post(api_dht_findprovs))
357 .route("/api/v0/dht/provide", post(api_dht_provide))
358 .route("/v1/stream/download/:cid", get(streaming::stream_download))
360 .route("/v1/stream/upload", post(streaming::stream_upload))
361 .route(
362 "/v1/progress/:operation_id",
363 get(streaming::progress_stream),
364 )
365 .route("/v1/block/batch/get", post(streaming::batch_get))
367 .route("/v1/block/batch/put", post(streaming::batch_put))
368 .route("/v1/block/batch/has", post(streaming::batch_has))
369 .route("/v1/tensor/:cid", get(tensor::get_tensor))
371 .route("/v1/tensor/:cid/info", get(tensor::get_tensor_info))
372 .route("/v1/tensor/:cid/arrow", get(tensor::get_tensor_arrow));
373
374 if self.state.auth.is_some() {
376 router = router
377 .route("/api/v0/auth/me", get(auth_handlers::me_handler))
378 .route(
379 "/api/v0/auth/permissions",
380 post(auth_handlers::update_permissions_handler),
381 )
382 .route(
383 "/api/v0/auth/deactivate/:username",
384 post(auth_handlers::deactivate_user_handler),
385 )
386 .route(
388 "/api/v0/auth/keys",
389 post(auth_handlers::create_api_key_handler),
390 )
391 .route(
392 "/api/v0/auth/keys",
393 get(auth_handlers::list_api_keys_handler),
394 )
395 .route(
396 "/api/v0/auth/keys/:key_id/revoke",
397 post(auth_handlers::revoke_api_key_handler),
398 )
399 .route(
400 "/api/v0/auth/keys/:key_id",
401 axum::routing::delete(auth_handlers::delete_api_key_handler),
402 );
403 }
404
405 let router = if self.config.compression_config.enable_gzip
410 || self.config.compression_config.enable_brotli
411 || self.config.compression_config.enable_deflate
412 {
413 router.layer(CompressionLayer::new())
414 } else {
415 router
416 };
417
418 router
419 .with_state(self.state.clone())
420 .layer(TraceLayer::new_for_http())
421 }
422
423 pub async fn start(self) -> CoreResult<()> {
425 let app = self.router();
426
427 self.print_endpoints();
429
430 if let Some(ref tls_config) = self.config.tls_config {
432 info!(
433 "Starting IPFRS HTTPS Gateway on {}",
434 self.config.listen_addr
435 );
436
437 let rustls_config = tls_config.build_server_config().await.map_err(|e| {
439 ipfrs_core::Error::Internal(format!("TLS configuration error: {}", e))
440 })?;
441
442 let addr: std::net::SocketAddr = self
444 .config
445 .listen_addr
446 .parse()
447 .map_err(|e| ipfrs_core::Error::Internal(format!("Invalid address: {}", e)))?;
448
449 info!("Gateway listening on https://{}", self.config.listen_addr);
450 info!("TLS/SSL enabled");
451
452 axum_server::bind_rustls(addr, rustls_config)
454 .serve(app.into_make_service())
455 .await
456 .map_err(|e| ipfrs_core::Error::Internal(format!("HTTPS server error: {}", e)))?;
457 } else {
458 info!("Starting IPFRS HTTP Gateway on {}", self.config.listen_addr);
459
460 let listener = tokio::net::TcpListener::bind(&self.config.listen_addr)
461 .await
462 .map_err(|e| {
463 ipfrs_core::Error::Internal(format!("Failed to bind to address: {}", e))
464 })?;
465
466 info!("Gateway listening on http://{}", self.config.listen_addr);
467 info!("Warning: TLS not enabled, using plain HTTP");
468
469 axum::serve(listener, app)
471 .await
472 .map_err(|e| ipfrs_core::Error::Internal(format!("HTTP server error: {}", e)))?;
473 }
474
475 Ok(())
476 }
477
478 fn print_endpoints(&self) {
480 info!("Endpoints:");
481 info!(" GET /health - Health check");
482 info!(" GET /ipfs/{{cid}} - Retrieve content");
483
484 if self.state.auth.is_some() {
486 info!(" POST /api/v0/auth/login - User login");
487 info!(" POST /api/v0/auth/register - User registration");
488 info!(" GET /api/v0/auth/me - Current user info");
489 info!(" POST /api/v0/auth/permissions - Update permissions (admin)");
490 info!(" POST /api/v0/auth/deactivate/:user - Deactivate user (admin)");
491 info!(" POST /api/v0/auth/keys - Create API key");
492 info!(" GET /api/v0/auth/keys - List API keys");
493 info!(" POST /api/v0/auth/keys/:id/revoke - Revoke API key");
494 info!(" DEL /api/v0/auth/keys/:id - Delete API key");
495 }
496
497 info!(" POST /api/v0/version - Get version");
498 info!(" POST /api/v0/add - Upload file");
499 info!(" POST /api/v0/block/get - Get block");
500 info!(" POST /api/v0/block/put - Store raw block");
501 info!(" POST /api/v0/block/stat - Get block stats");
502 info!(" POST /api/v0/cat - Output content");
503 info!(" POST /api/v0/dag/get - Get DAG node");
504 info!(" POST /api/v0/dag/put - Store DAG node");
505 info!(" POST /api/v0/dag/resolve - Resolve IPLD path");
506 info!(" POST /api/v0/semantic/index - Index content");
507 info!(" POST /api/v0/semantic/search - Search similar");
508 info!(" GET /api/v0/semantic/stats - Semantic stats");
509 info!(" POST /api/v0/semantic/save - Save semantic index");
510 info!(" POST /api/v0/semantic/load - Load semantic index");
511 info!(" POST /api/v0/logic/term - Store term");
512 info!(" GET /api/v0/logic/term/{{cid}} - Get term");
513 info!(" POST /api/v0/logic/predicate - Store predicate");
514 info!(" POST /api/v0/logic/rule - Store rule");
515 info!(" GET /api/v0/logic/stats - Logic stats");
516 info!(" POST /api/v0/logic/kb/save - Save knowledge base");
517 info!(" POST /api/v0/logic/kb/load - Load knowledge base");
518 info!(" GET /api/v0/id - Show peer ID");
519 info!(" GET /api/v0/swarm/peers - List peers");
520 info!(" POST /api/v0/swarm/connect - Connect to peer");
521 info!(" POST /api/v0/swarm/disconnect - Disconnect peer");
522 info!(" POST /api/v0/dht/findprovs - Find providers");
523 info!(" POST /api/v0/dht/provide - Announce content");
524
525 info!(" GET /v1/stream/download/:cid - Stream download");
527 info!(" POST /v1/stream/upload - Stream upload");
528 info!(" GET /v1/progress/:operation_id - SSE progress");
529
530 info!(" POST /v1/block/batch/get - Batch get blocks");
532 info!(" POST /v1/block/batch/put - Batch put blocks");
533 info!(" POST /v1/block/batch/has - Batch check blocks");
534 }
535
536 pub fn with_graphql(mut self) -> Self {
538 self.state = self.state.with_graphql();
539 self
540 }
541
542 pub fn with_auth(
544 mut self,
545 secret: &[u8],
546 default_admin_password: Option<&str>,
547 ) -> CoreResult<Self> {
548 self.state = self.state.with_auth(secret, default_admin_password)?;
549 Ok(self)
550 }
551
552 pub fn with_semantic(mut self, config: RouterConfig) -> CoreResult<Self> {
554 self.state = self.state.with_semantic(config)?;
555 Ok(self)
556 }
557
558 pub fn with_tensorlogic(mut self) -> CoreResult<Self> {
560 self.state = self.state.with_tensorlogic()?;
561 Ok(self)
562 }
563
564 pub fn with_network(mut self, network: ipfrs_network::NetworkNode) -> Self {
566 self.state = self.state.with_network(network);
567 self
568 }
569}
570
571async fn health_check() -> impl IntoResponse {
577 Json(serde_json::json!({
578 "status": "ok",
579 "service": "ipfrs-gateway",
580 "version": env!("CARGO_PKG_VERSION")
581 }))
582}
583
584async fn metrics_endpoint() -> impl IntoResponse {
588 match crate::metrics::encode_metrics() {
589 Ok(metrics) => (
590 StatusCode::OK,
591 [(header::CONTENT_TYPE, "text/plain; version=0.0.4")],
592 metrics,
593 )
594 .into_response(),
595 Err(e) => (
596 StatusCode::INTERNAL_SERVER_ERROR,
597 format!("Failed to encode metrics: {}", e),
598 )
599 .into_response(),
600 }
601}
602
603async fn get_content(
611 State(state): State<GatewayState>,
612 Path(cid_str): Path<String>,
613 headers: HeaderMap,
614) -> Result<Response, AppError> {
615 let cid: Cid = cid_str
616 .parse()
617 .map_err(|_| AppError::InvalidCid(cid_str.clone()))?;
618
619 let cache_config = CacheConfig::default();
621
622 if check_etag_match(&headers, &cid_str) {
624 return Ok(not_modified_response(&cid_str, &cache_config));
625 }
626
627 match state.store.get(&cid).await? {
628 Some(block) => {
629 let data = block.data();
630 let total_size = data.len();
631
632 if let Some(range_header) = headers.get(header::RANGE) {
634 if let Ok(range_str) = range_header.to_str() {
635 if let Some(ranges) = parse_multi_range(range_str, total_size) {
637 if ranges.len() == 1 {
638 let (start, end) = ranges[0];
640 let slice = &data[start..end];
641 let content_range =
642 format!("bytes {}-{}/{}", start, end - 1, total_size);
643
644 let mut response = Response::builder()
645 .status(StatusCode::PARTIAL_CONTENT)
646 .header(header::CONTENT_RANGE, content_range)
647 .header(header::CONTENT_LENGTH, slice.len().to_string())
648 .header(header::CONTENT_TYPE, "application/octet-stream")
649 .header(header::ACCEPT_RANGES, "bytes")
650 .body(Body::from(slice.to_vec()))
651 .unwrap();
652
653 add_caching_headers(response.headers_mut(), &cid_str, &cache_config);
655
656 return Ok(response);
657 } else {
658 return Ok(build_multipart_response(
660 data,
661 &ranges,
662 total_size,
663 &cid_str,
664 &cache_config,
665 ));
666 }
667 }
668 }
669 }
670
671 let mut response = Response::builder()
673 .status(StatusCode::OK)
674 .header(header::CONTENT_TYPE, "application/octet-stream")
675 .header(header::CONTENT_LENGTH, total_size.to_string())
676 .header(header::ACCEPT_RANGES, "bytes")
677 .body(Body::from(data.to_vec()))
678 .unwrap();
679
680 add_caching_headers(response.headers_mut(), &cid_str, &cache_config);
682
683 Ok(response)
684 }
685 None => Err(AppError::BlockNotFound(cid_str)),
686 }
687}
688
689#[allow(dead_code)]
692fn parse_range(range_str: &str, total_size: usize) -> Option<(usize, usize)> {
693 let range_str = range_str.strip_prefix("bytes=")?;
695
696 if let Some((start_str, end_str)) = range_str.split_once('-') {
697 let start: usize = start_str.parse().ok()?;
698
699 let end = if end_str.is_empty() {
700 total_size
701 } else {
702 end_str.parse::<usize>().ok()? + 1
703 };
704
705 if start < total_size && start < end && end <= total_size {
706 Some((start, end))
707 } else {
708 None
709 }
710 } else {
711 None
712 }
713}
714
715fn parse_multi_range(range_str: &str, total_size: usize) -> Option<Vec<(usize, usize)>> {
719 let range_str = range_str.strip_prefix("bytes=")?;
720
721 let mut ranges = Vec::new();
722
723 for part in range_str.split(',') {
724 let part = part.trim();
725 if let Some((start_str, end_str)) = part.split_once('-') {
726 if start_str.is_empty() {
728 let suffix_len: usize = end_str.parse().ok()?;
729 let start = total_size.saturating_sub(suffix_len);
730 ranges.push((start, total_size));
731 continue;
732 }
733
734 let start: usize = start_str.parse().ok()?;
735
736 let end = if end_str.is_empty() {
737 total_size
738 } else {
739 end_str.parse::<usize>().ok()? + 1
740 };
741
742 if start < total_size && start < end && end <= total_size {
743 ranges.push((start, end));
744 } else {
745 return None; }
747 } else {
748 return None; }
750 }
751
752 if ranges.is_empty() {
753 None
754 } else {
755 Some(merge_ranges(ranges))
757 }
758}
759
760fn merge_ranges(mut ranges: Vec<(usize, usize)>) -> Vec<(usize, usize)> {
762 if ranges.len() <= 1 {
763 return ranges;
764 }
765
766 ranges.sort_by_key(|r| r.0);
768
769 let mut merged = Vec::new();
770 let mut current = ranges[0];
771
772 for range in ranges.into_iter().skip(1) {
773 if range.0 <= current.1 {
775 current.1 = current.1.max(range.1);
777 } else {
778 merged.push(current);
779 current = range;
780 }
781 }
782 merged.push(current);
783
784 merged
785}
786
787fn build_multipart_response(
789 data: &[u8],
790 ranges: &[(usize, usize)],
791 total_size: usize,
792 cid: &str,
793 cache_config: &CacheConfig,
794) -> Response {
795 let boundary = format!("ipfrs_boundary_{:x}", rand::random::<u64>());
797
798 let mut body = Vec::new();
800
801 for (start, end) in ranges {
802 body.extend_from_slice(format!("--{}\r\n", boundary).as_bytes());
804 body.extend_from_slice(b"Content-Type: application/octet-stream\r\n");
805 body.extend_from_slice(
806 format!(
807 "Content-Range: bytes {}-{}/{}\r\n\r\n",
808 start,
809 end - 1,
810 total_size
811 )
812 .as_bytes(),
813 );
814
815 body.extend_from_slice(&data[*start..*end]);
817 body.extend_from_slice(b"\r\n");
818 }
819
820 body.extend_from_slice(format!("--{}--\r\n", boundary).as_bytes());
822
823 let content_type = format!("multipart/byteranges; boundary={}", boundary);
824
825 let mut response = Response::builder()
826 .status(StatusCode::PARTIAL_CONTENT)
827 .header(header::CONTENT_TYPE, content_type)
828 .header(header::CONTENT_LENGTH, body.len().to_string())
829 .header(header::ACCEPT_RANGES, "bytes")
830 .body(Body::from(body))
831 .unwrap();
832
833 add_caching_headers(response.headers_mut(), cid, cache_config);
835
836 response
837}
838
839#[derive(Serialize)]
841struct VersionResponse {
842 #[serde(rename = "Version")]
843 version: String,
844 #[serde(rename = "System")]
845 system: String,
846}
847
848async fn api_version() -> impl IntoResponse {
849 Json(VersionResponse {
850 version: env!("CARGO_PKG_VERSION").to_string(),
851 system: "ipfrs/0.3.0".to_string(),
852 })
853}
854
855#[derive(Deserialize)]
857struct BlockRequest {
858 arg: String,
859}
860
861async fn api_block_get(
862 State(state): State<GatewayState>,
863 Json(req): Json<BlockRequest>,
864) -> Result<Response, AppError> {
865 let cid: Cid = req
866 .arg
867 .parse()
868 .map_err(|_| AppError::InvalidCid(req.arg.clone()))?;
869
870 match state.store.get(&cid).await? {
871 Some(block) => Ok((StatusCode::OK, block.data().to_vec()).into_response()),
872 None => Err(AppError::BlockNotFound(req.arg)),
873 }
874}
875
876#[derive(Serialize)]
878struct BlockStatResponse {
879 #[serde(rename = "Key")]
880 key: String,
881 #[serde(rename = "Size")]
882 size: u64,
883}
884
885async fn api_block_stat(
886 State(state): State<GatewayState>,
887 Json(req): Json<BlockRequest>,
888) -> Result<Json<BlockStatResponse>, AppError> {
889 let cid: Cid = req
890 .arg
891 .parse()
892 .map_err(|_| AppError::InvalidCid(req.arg.clone()))?;
893
894 match state.store.get(&cid).await? {
895 Some(block) => Ok(Json(BlockStatResponse {
896 key: req.arg,
897 size: block.size(),
898 })),
899 None => Err(AppError::BlockNotFound(req.arg)),
900 }
901}
902
903async fn api_cat(
904 State(state): State<GatewayState>,
905 Json(req): Json<BlockRequest>,
906) -> Result<Response, AppError> {
907 let cid: Cid = req
908 .arg
909 .parse()
910 .map_err(|_| AppError::InvalidCid(req.arg.clone()))?;
911
912 match state.store.get(&cid).await? {
913 Some(block) => Ok((StatusCode::OK, block.data().to_vec()).into_response()),
914 None => Err(AppError::BlockNotFound(req.arg)),
915 }
916}
917
918#[derive(Serialize)]
920struct AddResponse {
921 #[serde(rename = "Name")]
922 name: String,
923 #[serde(rename = "Hash")]
924 hash: String,
925 #[serde(rename = "Size")]
926 size: String,
927}
928
929async fn api_add(
930 State(state): State<GatewayState>,
931 mut multipart: Multipart,
932) -> Result<Json<AddResponse>, AppError> {
933 use bytes::Bytes;
934 use ipfrs_core::Block;
935
936 if let Some(field) = multipart
938 .next_field()
939 .await
940 .map_err(|e| AppError::Upload(format!("Failed to read multipart field: {}", e)))?
941 {
942 let name = field.file_name().unwrap_or("upload").to_string();
943 let data = field
944 .bytes()
945 .await
946 .map_err(|e| AppError::Upload(format!("Failed to read file data: {}", e)))?;
947
948 let bytes_data = Bytes::from(data.to_vec());
950 let block = Block::new(bytes_data)
951 .map_err(|e| AppError::Upload(format!("Failed to create block: {}", e)))?;
952 let cid = *block.cid();
953 let size = block.size();
954
955 state.store.put(&block).await?;
957
958 info!("Added file '{}' as {}", name, cid);
959
960 Ok(Json(AddResponse {
961 name,
962 hash: cid.to_string(),
963 size: size.to_string(),
964 }))
965 } else {
966 Err(AppError::Upload("No file provided".to_string()))
967 }
968}
969
970async fn api_block_put(
972 State(state): State<GatewayState>,
973 body: axum::body::Bytes,
974) -> Result<Json<AddResponse>, AppError> {
975 use ipfrs_core::Block;
976
977 let block =
979 Block::new(body).map_err(|e| AppError::Upload(format!("Failed to create block: {}", e)))?;
980 let cid = *block.cid();
981 let size = block.size();
982
983 state.store.put(&block).await?;
985
986 info!("Stored raw block {}", cid);
987
988 Ok(Json(AddResponse {
989 name: cid.to_string(),
990 hash: cid.to_string(),
991 size: size.to_string(),
992 }))
993}
994
995#[derive(Deserialize)]
997struct DagRequest {
998 arg: String,
999}
1000
1001#[derive(Serialize)]
1003struct DagGetResponse {
1004 #[serde(rename = "Data")]
1005 data: String,
1006}
1007
1008async fn api_dag_get(
1009 State(state): State<GatewayState>,
1010 Json(req): Json<DagRequest>,
1011) -> Result<Json<DagGetResponse>, AppError> {
1012 let cid: Cid = req
1013 .arg
1014 .parse()
1015 .map_err(|_| AppError::InvalidCid(req.arg.clone()))?;
1016
1017 match state.store.get(&cid).await? {
1018 Some(block) => {
1019 let data_base64 =
1021 base64::Engine::encode(&base64::engine::general_purpose::STANDARD, block.data());
1022
1023 Ok(Json(DagGetResponse { data: data_base64 }))
1024 }
1025 None => Err(AppError::BlockNotFound(req.arg)),
1026 }
1027}
1028
1029#[derive(Serialize)]
1031struct DagPutResponse {
1032 #[serde(rename = "Cid")]
1033 cid: CidInfo,
1034}
1035
1036#[derive(Serialize)]
1037struct CidInfo {
1038 #[serde(rename = "/")]
1039 cid: String,
1040}
1041
1042async fn api_dag_put(
1043 State(state): State<GatewayState>,
1044 body: axum::body::Bytes,
1045) -> Result<Json<DagPutResponse>, AppError> {
1046 use ipfrs_core::Block;
1047
1048 let block = Block::new(body)
1050 .map_err(|e| AppError::Upload(format!("Failed to create DAG block: {}", e)))?;
1051 let cid = *block.cid();
1052
1053 state.store.put(&block).await?;
1055
1056 info!("Stored DAG node {}", cid);
1057
1058 Ok(Json(DagPutResponse {
1059 cid: CidInfo {
1060 cid: cid.to_string(),
1061 },
1062 }))
1063}
1064
1065#[derive(Deserialize)]
1067struct DagResolveRequest {
1068 arg: String,
1069}
1070
1071#[derive(Serialize)]
1073struct DagResolveResponse {
1074 #[serde(rename = "Cid")]
1075 cid: CidInfo,
1076 #[serde(rename = "RemPath")]
1077 rem_path: String,
1078}
1079
1080async fn api_dag_resolve(
1081 State(_state): State<GatewayState>,
1082 Json(req): Json<DagResolveRequest>,
1083) -> Result<Json<DagResolveResponse>, AppError> {
1084 let path = req.arg.trim_start_matches("/ipfs/");
1086 let parts: Vec<&str> = path.splitn(2, '/').collect();
1087
1088 let cid_str = parts[0];
1089 let cid: Cid = cid_str
1090 .parse()
1091 .map_err(|_| AppError::InvalidCid(cid_str.to_string()))?;
1092
1093 let sub_path = if parts.len() > 1 { parts[1] } else { "" };
1094
1095 Ok(Json(DagResolveResponse {
1098 cid: CidInfo {
1099 cid: cid.to_string(),
1100 },
1101 rem_path: sub_path.to_string(),
1102 }))
1103}
1104
1105#[derive(Deserialize)]
1111struct SemanticIndexRequest {
1112 cid: String,
1113 embedding: Vec<f32>,
1114}
1115
1116#[derive(Serialize)]
1118struct SemanticIndexResponse {
1119 indexed: bool,
1120}
1121
1122async fn api_semantic_index(
1123 State(state): State<GatewayState>,
1124 Json(req): Json<SemanticIndexRequest>,
1125) -> Result<Json<SemanticIndexResponse>, AppError> {
1126 let semantic = state
1127 .semantic
1128 .as_ref()
1129 .ok_or_else(|| AppError::FeatureDisabled("Semantic search not enabled".to_string()))?;
1130
1131 let cid: Cid = req
1132 .cid
1133 .parse()
1134 .map_err(|_| AppError::InvalidCid(req.cid.clone()))?;
1135
1136 semantic
1137 .add(&cid, &req.embedding)
1138 .map_err(|e| AppError::Semantic(format!("Failed to index: {}", e)))?;
1139
1140 info!("Indexed content {} with embedding", cid);
1141
1142 Ok(Json(SemanticIndexResponse { indexed: true }))
1143}
1144
1145#[derive(Deserialize)]
1147struct SemanticSearchRequest {
1148 query: Vec<f32>,
1149 k: Option<usize>,
1150 filter: Option<QueryFilter>,
1151}
1152
1153#[derive(Serialize)]
1155struct SemanticSearchResponse {
1156 results: Vec<SearchResultJson>,
1157}
1158
1159#[derive(Serialize)]
1160struct SearchResultJson {
1161 cid: String,
1162 score: f32,
1163}
1164
1165async fn api_semantic_search(
1166 State(state): State<GatewayState>,
1167 Json(req): Json<SemanticSearchRequest>,
1168) -> Result<Json<SemanticSearchResponse>, AppError> {
1169 let semantic = state
1170 .semantic
1171 .as_ref()
1172 .ok_or_else(|| AppError::FeatureDisabled("Semantic search not enabled".to_string()))?;
1173
1174 let k = req.k.unwrap_or(10);
1175
1176 let results = if let Some(filter) = req.filter {
1177 semantic
1178 .query_with_filter(&req.query, k, filter)
1179 .await
1180 .map_err(|e| AppError::Semantic(format!("Search failed: {}", e)))?
1181 } else {
1182 semantic
1183 .query(&req.query, k)
1184 .await
1185 .map_err(|e| AppError::Semantic(format!("Search failed: {}", e)))?
1186 };
1187
1188 let results_json: Vec<SearchResultJson> = results
1189 .into_iter()
1190 .map(|r| SearchResultJson {
1191 cid: r.cid.to_string(),
1192 score: r.score,
1193 })
1194 .collect();
1195
1196 Ok(Json(SemanticSearchResponse {
1197 results: results_json,
1198 }))
1199}
1200
1201#[derive(Serialize)]
1203struct SemanticStatsResponse {
1204 num_vectors: usize,
1205 dimension: usize,
1206 metric: String,
1207 cache_size: usize,
1208 cache_capacity: usize,
1209}
1210
1211async fn api_semantic_stats(
1212 State(state): State<GatewayState>,
1213) -> Result<Json<SemanticStatsResponse>, AppError> {
1214 let semantic = state
1215 .semantic
1216 .as_ref()
1217 .ok_or_else(|| AppError::FeatureDisabled("Semantic search not enabled".to_string()))?;
1218
1219 let router_stats = semantic.stats();
1220 let cache_stats = semantic.cache_stats();
1221
1222 let metric_str = match router_stats.metric {
1223 DistanceMetric::Cosine => "cosine",
1224 DistanceMetric::L2 => "l2",
1225 DistanceMetric::DotProduct => "dotproduct",
1226 };
1227
1228 Ok(Json(SemanticStatsResponse {
1229 num_vectors: router_stats.num_vectors,
1230 dimension: router_stats.dimension,
1231 metric: metric_str.to_string(),
1232 cache_size: cache_stats.size,
1233 cache_capacity: cache_stats.capacity,
1234 }))
1235}
1236
1237#[derive(Deserialize)]
1239struct SemanticSaveRequest {
1240 path: String,
1241}
1242
1243#[derive(Serialize)]
1245struct SemanticSaveResponse {
1246 success: bool,
1247 path: String,
1248}
1249
1250async fn api_semantic_save(
1251 State(state): State<GatewayState>,
1252 Json(req): Json<SemanticSaveRequest>,
1253) -> Result<Json<SemanticSaveResponse>, AppError> {
1254 let semantic = state
1255 .semantic
1256 .as_ref()
1257 .ok_or_else(|| AppError::FeatureDisabled("Semantic search not enabled".to_string()))?;
1258
1259 semantic
1260 .save_index(&req.path)
1261 .await
1262 .map_err(|e| AppError::Semantic(format!("Failed to save index: {}", e)))?;
1263
1264 info!("Saved semantic index to {}", req.path);
1265
1266 Ok(Json(SemanticSaveResponse {
1267 success: true,
1268 path: req.path,
1269 }))
1270}
1271
1272#[derive(Deserialize)]
1274struct SemanticLoadRequest {
1275 path: String,
1276}
1277
1278#[derive(Serialize)]
1280struct SemanticLoadResponse {
1281 success: bool,
1282 path: String,
1283}
1284
1285async fn api_semantic_load(
1286 State(state): State<GatewayState>,
1287 Json(req): Json<SemanticLoadRequest>,
1288) -> Result<Json<SemanticLoadResponse>, AppError> {
1289 let semantic = state
1290 .semantic
1291 .as_ref()
1292 .ok_or_else(|| AppError::FeatureDisabled("Semantic search not enabled".to_string()))?;
1293
1294 semantic
1295 .load_index(&req.path)
1296 .await
1297 .map_err(|e| AppError::Semantic(format!("Failed to load index: {}", e)))?;
1298
1299 info!("Loaded semantic index from {}", req.path);
1300
1301 Ok(Json(SemanticLoadResponse {
1302 success: true,
1303 path: req.path,
1304 }))
1305}
1306
1307#[derive(Deserialize)]
1313struct LogicStoreTermRequest {
1314 term: Term,
1315}
1316
1317#[derive(Serialize)]
1319struct LogicStoreResponse {
1320 cid: String,
1321}
1322
1323async fn api_logic_store_term(
1324 State(state): State<GatewayState>,
1325 Json(req): Json<LogicStoreTermRequest>,
1326) -> Result<Json<LogicStoreResponse>, AppError> {
1327 let tensorlogic = state
1328 .tensorlogic
1329 .as_ref()
1330 .ok_or_else(|| AppError::FeatureDisabled("TensorLogic not enabled".to_string()))?;
1331
1332 let cid = tensorlogic
1333 .store_term(&req.term)
1334 .await
1335 .map_err(|e| AppError::Logic(format!("Failed to store term: {}", e)))?;
1336
1337 info!("Stored term as {}", cid);
1338
1339 Ok(Json(LogicStoreResponse {
1340 cid: cid.to_string(),
1341 }))
1342}
1343
1344#[derive(Serialize)]
1346struct LogicGetTermResponse {
1347 term: Term,
1348}
1349
1350async fn api_logic_get_term(
1351 State(state): State<GatewayState>,
1352 Path(cid_str): Path<String>,
1353) -> Result<Json<LogicGetTermResponse>, AppError> {
1354 let tensorlogic = state
1355 .tensorlogic
1356 .as_ref()
1357 .ok_or_else(|| AppError::FeatureDisabled("TensorLogic not enabled".to_string()))?;
1358
1359 let cid: Cid = cid_str
1360 .parse()
1361 .map_err(|_| AppError::InvalidCid(cid_str.clone()))?;
1362
1363 let term = tensorlogic
1364 .get_term(&cid)
1365 .await
1366 .map_err(|e| AppError::Logic(format!("Failed to get term: {}", e)))?
1367 .ok_or_else(|| AppError::NotFound(format!("Term not found: {}", cid_str)))?;
1368
1369 Ok(Json(LogicGetTermResponse { term }))
1370}
1371
1372#[derive(Deserialize)]
1374struct LogicStorePredicateRequest {
1375 predicate: Predicate,
1376}
1377
1378async fn api_logic_store_predicate(
1379 State(state): State<GatewayState>,
1380 Json(req): Json<LogicStorePredicateRequest>,
1381) -> Result<Json<LogicStoreResponse>, AppError> {
1382 let tensorlogic = state
1383 .tensorlogic
1384 .as_ref()
1385 .ok_or_else(|| AppError::FeatureDisabled("TensorLogic not enabled".to_string()))?;
1386
1387 let cid = tensorlogic
1388 .store_predicate(&req.predicate)
1389 .await
1390 .map_err(|e| AppError::Logic(format!("Failed to store predicate: {}", e)))?;
1391
1392 info!("Stored predicate as {}", cid);
1393
1394 Ok(Json(LogicStoreResponse {
1395 cid: cid.to_string(),
1396 }))
1397}
1398
1399#[derive(Deserialize)]
1401struct LogicStoreRuleRequest {
1402 rule: Rule,
1403}
1404
1405async fn api_logic_store_rule(
1406 State(state): State<GatewayState>,
1407 Json(req): Json<LogicStoreRuleRequest>,
1408) -> Result<Json<LogicStoreResponse>, AppError> {
1409 let tensorlogic = state
1410 .tensorlogic
1411 .as_ref()
1412 .ok_or_else(|| AppError::FeatureDisabled("TensorLogic not enabled".to_string()))?;
1413
1414 let cid = tensorlogic
1415 .store_rule(&req.rule)
1416 .await
1417 .map_err(|e| AppError::Logic(format!("Failed to store rule: {}", e)))?;
1418
1419 info!("Stored rule as {}", cid);
1420
1421 Ok(Json(LogicStoreResponse {
1422 cid: cid.to_string(),
1423 }))
1424}
1425
1426#[derive(Serialize)]
1428struct LogicStatsResponse {
1429 enabled: bool,
1430}
1431
1432async fn api_logic_stats(
1433 State(state): State<GatewayState>,
1434) -> Result<Json<LogicStatsResponse>, AppError> {
1435 let enabled = state.tensorlogic.is_some();
1436
1437 Ok(Json(LogicStatsResponse { enabled }))
1438}
1439
1440#[derive(Deserialize)]
1442struct AddFactRequest {
1443 fact: Predicate,
1444}
1445
1446#[derive(Serialize)]
1448struct AddFactResponse {
1449 success: bool,
1450}
1451
1452async fn api_logic_add_fact(
1453 State(state): State<GatewayState>,
1454 Json(req): Json<AddFactRequest>,
1455) -> Result<Json<AddFactResponse>, AppError> {
1456 let tensorlogic = state
1457 .tensorlogic
1458 .as_ref()
1459 .ok_or_else(|| AppError::FeatureDisabled("TensorLogic not enabled".to_string()))?;
1460
1461 tensorlogic
1462 .add_fact(req.fact)
1463 .map_err(|e| AppError::Logic(format!("Failed to add fact: {}", e)))?;
1464
1465 Ok(Json(AddFactResponse { success: true }))
1466}
1467
1468#[derive(Deserialize)]
1470struct AddRuleRequest {
1471 rule: Rule,
1472}
1473
1474#[derive(Serialize)]
1476struct AddRuleResponse {
1477 success: bool,
1478}
1479
1480async fn api_logic_add_rule(
1481 State(state): State<GatewayState>,
1482 Json(req): Json<AddRuleRequest>,
1483) -> Result<Json<AddRuleResponse>, AppError> {
1484 let tensorlogic = state
1485 .tensorlogic
1486 .as_ref()
1487 .ok_or_else(|| AppError::FeatureDisabled("TensorLogic not enabled".to_string()))?;
1488
1489 tensorlogic
1490 .add_rule(req.rule)
1491 .map_err(|e| AppError::Logic(format!("Failed to add rule: {}", e)))?;
1492
1493 Ok(Json(AddRuleResponse { success: true }))
1494}
1495
1496#[derive(Deserialize)]
1498struct InferRequest {
1499 goal: Predicate,
1500}
1501
1502#[derive(Serialize)]
1504struct InferResponse {
1505 solutions: Vec<Substitution>,
1506}
1507
1508async fn api_logic_infer(
1509 State(state): State<GatewayState>,
1510 Json(req): Json<InferRequest>,
1511) -> Result<Json<InferResponse>, AppError> {
1512 let tensorlogic = state
1513 .tensorlogic
1514 .as_ref()
1515 .ok_or_else(|| AppError::FeatureDisabled("TensorLogic not enabled".to_string()))?;
1516
1517 let solutions = tensorlogic
1518 .infer(&req.goal)
1519 .map_err(|e| AppError::Logic(format!("Inference failed: {}", e)))?;
1520
1521 Ok(Json(InferResponse { solutions }))
1522}
1523
1524#[derive(Deserialize)]
1526struct ProveRequest {
1527 goal: Predicate,
1528}
1529
1530#[derive(Serialize)]
1532struct ProveResponse {
1533 proof: Option<Proof>,
1534 cid: Option<String>,
1535}
1536
1537#[derive(Deserialize)]
1539struct VerifyRequest {
1540 proof: Proof,
1541}
1542
1543#[derive(Serialize)]
1545struct VerifyResponse {
1546 valid: bool,
1547}
1548
1549async fn api_logic_prove(
1550 State(state): State<GatewayState>,
1551 Json(req): Json<ProveRequest>,
1552) -> Result<Json<ProveResponse>, AppError> {
1553 let tensorlogic = state
1554 .tensorlogic
1555 .as_ref()
1556 .ok_or_else(|| AppError::FeatureDisabled("TensorLogic not enabled".to_string()))?;
1557
1558 let proof = tensorlogic
1559 .prove(&req.goal)
1560 .map_err(|e| AppError::Logic(format!("Proof generation failed: {}", e)))?;
1561
1562 if let Some(ref p) = proof {
1563 let cid = tensorlogic
1564 .store_proof(p)
1565 .await
1566 .map_err(|e| AppError::Logic(format!("Failed to store proof: {}", e)))?;
1567
1568 Ok(Json(ProveResponse {
1569 proof,
1570 cid: Some(cid.to_string()),
1571 }))
1572 } else {
1573 Ok(Json(ProveResponse {
1574 proof: None,
1575 cid: None,
1576 }))
1577 }
1578}
1579
1580async fn api_logic_verify(
1581 State(state): State<GatewayState>,
1582 Json(req): Json<VerifyRequest>,
1583) -> Result<Json<VerifyResponse>, AppError> {
1584 let tensorlogic = state
1585 .tensorlogic
1586 .as_ref()
1587 .ok_or_else(|| AppError::FeatureDisabled("TensorLogic not enabled".to_string()))?;
1588
1589 let valid = tensorlogic
1590 .verify_proof(&req.proof)
1591 .map_err(|e| AppError::Logic(format!("Proof verification failed: {}", e)))?;
1592
1593 Ok(Json(VerifyResponse { valid }))
1594}
1595
1596#[derive(Serialize)]
1598struct GetProofResponse {
1599 proof: Proof,
1600}
1601
1602async fn api_logic_get_proof(
1603 State(state): State<GatewayState>,
1604 Path(cid_str): Path<String>,
1605) -> Result<Json<GetProofResponse>, AppError> {
1606 let tensorlogic = state
1607 .tensorlogic
1608 .as_ref()
1609 .ok_or_else(|| AppError::FeatureDisabled("TensorLogic not enabled".to_string()))?;
1610
1611 let cid: Cid = cid_str
1612 .parse()
1613 .map_err(|e| AppError::InvalidCid(format!("Invalid CID: {}", e)))?;
1614
1615 let proof = tensorlogic
1616 .get_proof(&cid)
1617 .await
1618 .map_err(|e| AppError::Logic(format!("Failed to get proof: {}", e)))?
1619 .ok_or_else(|| AppError::NotFound(format!("Proof not found: {}", cid)))?;
1620
1621 Ok(Json(GetProofResponse { proof }))
1622}
1623
1624#[derive(Serialize)]
1626struct KbStatsResponse {
1627 num_facts: usize,
1628 num_rules: usize,
1629}
1630
1631async fn api_logic_kb_stats(
1632 State(state): State<GatewayState>,
1633) -> Result<Json<KbStatsResponse>, AppError> {
1634 let tensorlogic = state
1635 .tensorlogic
1636 .as_ref()
1637 .ok_or_else(|| AppError::FeatureDisabled("TensorLogic not enabled".to_string()))?;
1638
1639 let kb_stats = tensorlogic.kb_stats();
1640
1641 Ok(Json(KbStatsResponse {
1642 num_facts: kb_stats.num_facts,
1643 num_rules: kb_stats.num_rules,
1644 }))
1645}
1646
1647#[derive(Deserialize)]
1649struct KbSaveRequest {
1650 path: String,
1651}
1652
1653#[derive(Serialize)]
1655struct KbSaveResponse {
1656 success: bool,
1657 path: String,
1658}
1659
1660async fn api_logic_kb_save(
1661 State(state): State<GatewayState>,
1662 Json(req): Json<KbSaveRequest>,
1663) -> Result<Json<KbSaveResponse>, AppError> {
1664 let tensorlogic = state
1665 .tensorlogic
1666 .as_ref()
1667 .ok_or_else(|| AppError::FeatureDisabled("TensorLogic not enabled".to_string()))?;
1668
1669 tensorlogic
1670 .save_kb(&req.path)
1671 .await
1672 .map_err(|e| AppError::Logic(format!("Failed to save knowledge base: {}", e)))?;
1673
1674 info!("Saved knowledge base to {}", req.path);
1675
1676 Ok(Json(KbSaveResponse {
1677 success: true,
1678 path: req.path,
1679 }))
1680}
1681
1682#[derive(Deserialize)]
1684struct KbLoadRequest {
1685 path: String,
1686}
1687
1688#[derive(Serialize)]
1690struct KbLoadResponse {
1691 success: bool,
1692 path: String,
1693}
1694
1695async fn api_logic_kb_load(
1696 State(state): State<GatewayState>,
1697 Json(req): Json<KbLoadRequest>,
1698) -> Result<Json<KbLoadResponse>, AppError> {
1699 let tensorlogic = state
1700 .tensorlogic
1701 .as_ref()
1702 .ok_or_else(|| AppError::FeatureDisabled("TensorLogic not enabled".to_string()))?;
1703
1704 tensorlogic
1705 .load_kb(&req.path)
1706 .await
1707 .map_err(|e| AppError::Logic(format!("Failed to load knowledge base: {}", e)))?;
1708
1709 info!("Loaded knowledge base from {}", req.path);
1710
1711 Ok(Json(KbLoadResponse {
1712 success: true,
1713 path: req.path,
1714 }))
1715}
1716
1717#[derive(Serialize)]
1723struct NetworkIdResponse {
1724 #[serde(rename = "ID")]
1725 id: String,
1726 #[serde(rename = "Addresses")]
1727 addresses: Vec<String>,
1728}
1729
1730async fn api_network_id(
1731 State(state): State<GatewayState>,
1732) -> Result<Json<NetworkIdResponse>, AppError> {
1733 let network = state
1734 .network
1735 .as_ref()
1736 .ok_or_else(|| AppError::FeatureDisabled("Network not enabled".to_string()))?;
1737
1738 let (peer_id, listeners) = {
1739 let network = network.lock().await;
1740 (network.peer_id().to_string(), network.listeners())
1741 };
1742
1743 let addresses = listeners
1744 .iter()
1745 .map(|addr| format!("{}/p2p/{}", addr, peer_id))
1746 .collect();
1747
1748 Ok(Json(NetworkIdResponse {
1749 id: peer_id,
1750 addresses,
1751 }))
1752}
1753
1754#[derive(Serialize)]
1756struct SwarmPeersResponse {
1757 #[serde(rename = "Peers")]
1758 peers: Vec<PeerEntry>,
1759}
1760
1761#[derive(Serialize)]
1762struct PeerEntry {
1763 #[serde(rename = "Peer")]
1764 peer: String,
1765}
1766
1767async fn api_swarm_peers(
1768 State(state): State<GatewayState>,
1769) -> Result<Json<SwarmPeersResponse>, AppError> {
1770 let network = state
1771 .network
1772 .as_ref()
1773 .ok_or_else(|| AppError::FeatureDisabled("Network not enabled".to_string()))?;
1774
1775 let peers = {
1776 let network = network.lock().await;
1777 network.connected_peers()
1778 };
1779
1780 let peer_entries: Vec<PeerEntry> = peers
1781 .into_iter()
1782 .map(|p| PeerEntry {
1783 peer: p.to_string(),
1784 })
1785 .collect();
1786
1787 Ok(Json(SwarmPeersResponse {
1788 peers: peer_entries,
1789 }))
1790}
1791
1792#[derive(Deserialize)]
1794struct SwarmConnectRequest {
1795 arg: String,
1796}
1797
1798#[derive(Serialize)]
1800struct SwarmConnectResponse {
1801 #[serde(rename = "Strings")]
1802 strings: Vec<String>,
1803}
1804
1805async fn api_swarm_connect(
1806 State(state): State<GatewayState>,
1807 Json(req): Json<SwarmConnectRequest>,
1808) -> Result<Json<SwarmConnectResponse>, AppError> {
1809 let network = state
1810 .network
1811 .as_ref()
1812 .ok_or_else(|| AppError::FeatureDisabled("Network not enabled".to_string()))?;
1813
1814 let addr: ipfrs_network::libp2p::Multiaddr = req
1815 .arg
1816 .parse()
1817 .map_err(|e| AppError::Network(format!("Invalid multiaddr: {}", e)))?;
1818
1819 {
1820 let mut network = network.lock().await;
1821 network
1822 .connect(addr.clone())
1823 .await
1824 .map_err(|e| AppError::Network(format!("Connect failed: {}", e)))?;
1825 }
1826
1827 info!("Connected to peer: {}", req.arg);
1828
1829 Ok(Json(SwarmConnectResponse {
1830 strings: vec![format!("connect {} success", req.arg)],
1831 }))
1832}
1833
1834#[derive(Deserialize)]
1836struct SwarmDisconnectRequest {
1837 arg: String,
1838}
1839
1840#[derive(Serialize)]
1842struct SwarmDisconnectResponse {
1843 #[serde(rename = "Strings")]
1844 strings: Vec<String>,
1845}
1846
1847async fn api_swarm_disconnect(
1848 State(state): State<GatewayState>,
1849 Json(req): Json<SwarmDisconnectRequest>,
1850) -> Result<Json<SwarmDisconnectResponse>, AppError> {
1851 let network = state
1852 .network
1853 .as_ref()
1854 .ok_or_else(|| AppError::FeatureDisabled("Network not enabled".to_string()))?;
1855
1856 let peer_id: ipfrs_network::libp2p::PeerId = req
1857 .arg
1858 .parse()
1859 .map_err(|e| AppError::Network(format!("Invalid peer ID: {}", e)))?;
1860
1861 {
1862 let mut network = network.lock().await;
1863 network
1864 .disconnect(peer_id)
1865 .await
1866 .map_err(|e| AppError::Network(format!("Disconnect failed: {}", e)))?;
1867 }
1868
1869 info!("Disconnected from peer: {}", req.arg);
1870
1871 Ok(Json(SwarmDisconnectResponse {
1872 strings: vec![format!("disconnect {} success", req.arg)],
1873 }))
1874}
1875
1876#[derive(Deserialize)]
1878struct DhtFindprovsRequest {
1879 arg: String,
1880}
1881
1882#[derive(Serialize)]
1884struct DhtFindprovsResponse {
1885 #[serde(rename = "Responses")]
1886 responses: Vec<DhtProviderEntry>,
1887}
1888
1889#[derive(Serialize)]
1890struct DhtProviderEntry {
1891 #[serde(rename = "ID")]
1892 id: String,
1893}
1894
1895async fn api_dht_findprovs(
1896 State(state): State<GatewayState>,
1897 Json(req): Json<DhtFindprovsRequest>,
1898) -> Result<Json<DhtFindprovsResponse>, AppError> {
1899 let network = state
1900 .network
1901 .as_ref()
1902 .ok_or_else(|| AppError::FeatureDisabled("Network not enabled".to_string()))?;
1903
1904 let cid: Cid = req
1905 .arg
1906 .parse()
1907 .map_err(|_| AppError::InvalidCid(req.arg.clone()))?;
1908
1909 {
1910 let mut network = network.lock().await;
1911 network
1912 .find_providers(&cid)
1913 .await
1914 .map_err(|e| AppError::Network(format!("Find providers failed: {}", e)))?;
1915 }
1916
1917 info!("Finding providers for: {}", req.arg);
1918
1919 Ok(Json(DhtFindprovsResponse { responses: vec![] }))
1923}
1924
1925#[derive(Deserialize)]
1927struct DhtProvideRequest {
1928 arg: String,
1929}
1930
1931#[derive(Serialize)]
1933struct DhtProvideResponse {
1934 #[serde(rename = "ID")]
1935 id: String,
1936}
1937
1938async fn api_dht_provide(
1939 State(state): State<GatewayState>,
1940 Json(req): Json<DhtProvideRequest>,
1941) -> Result<Json<DhtProvideResponse>, AppError> {
1942 let network = state
1943 .network
1944 .as_ref()
1945 .ok_or_else(|| AppError::FeatureDisabled("Network not enabled".to_string()))?;
1946
1947 let cid: Cid = req
1948 .arg
1949 .parse()
1950 .map_err(|_| AppError::InvalidCid(req.arg.clone()))?;
1951
1952 {
1953 let mut network = network.lock().await;
1954 network
1955 .provide(&cid)
1956 .await
1957 .map_err(|e| AppError::Network(format!("Provide failed: {}", e)))?;
1958 }
1959
1960 info!("Announcing content to DHT: {}", req.arg);
1961
1962 Ok(Json(DhtProvideResponse { id: req.arg }))
1963}
1964
1965async fn graphql_handler(
1971 State(state): State<GatewayState>,
1972 req: GraphQLRequest,
1973) -> Result<GraphQLResponse, AppError> {
1974 let schema = state
1975 .graphql_schema
1976 .as_ref()
1977 .ok_or_else(|| AppError::FeatureDisabled("GraphQL not enabled".to_string()))?;
1978
1979 Ok(schema.execute(req.into_inner()).await.into())
1980}
1981
1982async fn graphql_playground() -> impl IntoResponse {
1984 Html(playground_source(GraphQLPlaygroundConfig::new("/graphql")))
1985}
1986
1987#[derive(Debug)]
1993enum AppError {
1994 InvalidCid(String),
1995 BlockNotFound(String),
1996 NotFound(String),
1997 Upload(String),
1998 Storage(ipfrs_core::Error),
1999 FeatureDisabled(String),
2000 Semantic(String),
2001 Logic(String),
2002 Network(String),
2003}
2004
2005impl From<ipfrs_core::Error> for AppError {
2006 fn from(err: ipfrs_core::Error) -> Self {
2007 AppError::Storage(err)
2008 }
2009}
2010
2011impl IntoResponse for AppError {
2012 fn into_response(self) -> Response {
2013 let (status, message) = match self {
2014 AppError::InvalidCid(cid) => (StatusCode::BAD_REQUEST, format!("Invalid CID: {}", cid)),
2015 AppError::BlockNotFound(cid) => {
2016 (StatusCode::NOT_FOUND, format!("Block not found: {}", cid))
2017 }
2018 AppError::NotFound(msg) => (StatusCode::NOT_FOUND, msg),
2019 AppError::Upload(msg) => {
2020 error!("Upload error: {}", msg);
2021 (StatusCode::BAD_REQUEST, format!("Upload error: {}", msg))
2022 }
2023 AppError::Storage(err) => {
2024 error!("Storage error: {}", err);
2025 (
2026 StatusCode::INTERNAL_SERVER_ERROR,
2027 format!("Storage error: {}", err),
2028 )
2029 }
2030 AppError::FeatureDisabled(msg) => (
2031 StatusCode::SERVICE_UNAVAILABLE,
2032 format!("Feature not available: {}", msg),
2033 ),
2034 AppError::Semantic(msg) => {
2035 error!("Semantic error: {}", msg);
2036 (
2037 StatusCode::INTERNAL_SERVER_ERROR,
2038 format!("Semantic error: {}", msg),
2039 )
2040 }
2041 AppError::Logic(msg) => {
2042 error!("Logic error: {}", msg);
2043 (
2044 StatusCode::INTERNAL_SERVER_ERROR,
2045 format!("Logic error: {}", msg),
2046 )
2047 }
2048 AppError::Network(msg) => {
2049 error!("Network error: {}", msg);
2050 (
2051 StatusCode::INTERNAL_SERVER_ERROR,
2052 format!("Network error: {}", msg),
2053 )
2054 }
2055 };
2056
2057 (status, message).into_response()
2058 }
2059}
2060
2061#[cfg(test)]
2062mod tests {
2063 use super::*;
2064
2065 #[test]
2066 fn test_parse_single_range() {
2067 assert_eq!(parse_range("bytes=0-100", 1000), Some((0, 101)));
2069
2070 assert_eq!(parse_range("bytes=500-", 1000), Some((500, 1000)));
2072
2073 assert_eq!(parse_range("bytes=1000-1100", 1000), None);
2075
2076 assert_eq!(parse_range("bytes=500-100", 1000), None);
2078
2079 assert_eq!(parse_range("bytes=abc-100", 1000), None);
2081 assert_eq!(parse_range("invalid", 1000), None);
2082 }
2083
2084 #[test]
2085 fn test_parse_multi_range() {
2086 let ranges = parse_multi_range("bytes=0-100", 1000);
2088 assert_eq!(ranges, Some(vec![(0, 101)]));
2089
2090 let ranges = parse_multi_range("bytes=0-100,200-300", 1000);
2092 assert_eq!(ranges, Some(vec![(0, 101), (200, 301)]));
2093
2094 let ranges = parse_multi_range("bytes=0-100, 200-300, 500-600", 1000);
2096 assert_eq!(ranges, Some(vec![(0, 101), (200, 301), (500, 601)]));
2097
2098 let ranges = parse_multi_range("bytes=-500", 1000);
2100 assert_eq!(ranges, Some(vec![(500, 1000)]));
2101
2102 assert_eq!(parse_multi_range("bytes=1000-1100", 1000), None);
2104
2105 assert_eq!(parse_multi_range("invalid", 1000), None);
2107 }
2108
2109 #[test]
2110 fn test_merge_ranges() {
2111 let ranges = vec![(0, 100), (200, 300)];
2113 assert_eq!(merge_ranges(ranges), vec![(0, 100), (200, 300)]);
2114
2115 let ranges = vec![(0, 150), (100, 200)];
2117 assert_eq!(merge_ranges(ranges), vec![(0, 200)]);
2118
2119 let ranges = vec![(0, 100), (100, 200)];
2121 assert_eq!(merge_ranges(ranges), vec![(0, 200)]);
2122
2123 let ranges = vec![(200, 300), (0, 100), (50, 150)];
2125 assert_eq!(merge_ranges(ranges), vec![(0, 150), (200, 300)]);
2126
2127 let ranges = vec![(50, 100)];
2129 assert_eq!(merge_ranges(ranges), vec![(50, 100)]);
2130
2131 let ranges: Vec<(usize, usize)> = vec![];
2133 assert_eq!(merge_ranges(ranges), vec![]);
2134 }
2135
2136 #[test]
2137 fn test_build_multipart_response() {
2138 let data = b"Hello, World! This is test data for multi-range requests.";
2139 let ranges = vec![(0, 5), (7, 12)];
2140 let total_size = data.len();
2141 let config = CacheConfig::default();
2142
2143 let response = build_multipart_response(data, &ranges, total_size, "QmTest123", &config);
2144
2145 assert_eq!(response.status(), StatusCode::PARTIAL_CONTENT);
2146
2147 let content_type = response
2148 .headers()
2149 .get(header::CONTENT_TYPE)
2150 .unwrap()
2151 .to_str()
2152 .unwrap();
2153 assert!(content_type.starts_with("multipart/byteranges"));
2154 assert!(content_type.contains("boundary="));
2155
2156 assert!(response.headers().contains_key(header::ETAG));
2158 assert!(response.headers().contains_key(header::CACHE_CONTROL));
2159 }
2160
2161 #[test]
2162 fn test_config_presets() {
2163 let prod = GatewayConfig::production();
2165 assert_eq!(prod.listen_addr, "0.0.0.0:8080");
2166 assert!(prod.compression_config.enable_gzip);
2167 assert!(prod.compression_config.enable_brotli);
2168 assert!(prod.compression_config.enable_deflate);
2169
2170 let dev = GatewayConfig::development();
2172 assert_eq!(dev.listen_addr, "127.0.0.1:8080");
2173 assert!(dev.compression_config.enable_gzip);
2174 assert!(!dev.compression_config.enable_brotli);
2175
2176 let test = GatewayConfig::testing();
2178 assert_eq!(test.listen_addr, "127.0.0.1:0");
2179 assert!(!test.compression_config.enable_gzip);
2180 assert!(!test.compression_config.enable_brotli);
2181 }
2182
2183 #[test]
2184 fn test_config_builders() {
2185 let config = GatewayConfig::default()
2186 .with_listen_addr("0.0.0.0:9090")
2187 .with_storage_path("/custom/path")
2188 .with_cache_mb(200)
2189 .with_full_compression();
2190
2191 assert_eq!(config.listen_addr, "0.0.0.0:9090");
2192 assert!(config.compression_config.enable_gzip);
2193 assert!(config.compression_config.enable_brotli);
2194 assert!(config.compression_config.enable_deflate);
2195 }
2196
2197 #[test]
2198 fn test_config_validation() {
2199 let valid_config = GatewayConfig::default();
2201 assert!(valid_config.validate().is_ok());
2202
2203 let invalid_addr = GatewayConfig {
2205 listen_addr: "invalid-address".to_string(),
2206 ..Default::default()
2207 };
2208 assert!(invalid_addr.validate().is_err());
2209
2210 let empty_addr = GatewayConfig {
2212 listen_addr: "".to_string(),
2213 ..Default::default()
2214 };
2215 assert!(empty_addr.validate().is_err());
2216 }
2217
2218 #[test]
2219 fn test_compression_helpers() {
2220 let config_with = GatewayConfig::default().with_full_compression();
2221 assert!(config_with.compression_config.enable_gzip);
2222 assert!(config_with.compression_config.enable_brotli);
2223 assert!(config_with.compression_config.enable_deflate);
2224
2225 let config_without = GatewayConfig::default().without_compression();
2226 assert!(!config_without.compression_config.enable_gzip);
2227 assert!(!config_without.compression_config.enable_brotli);
2228 assert!(!config_without.compression_config.enable_deflate);
2229 }
2230}