feagi_api/transports/http/
server.rs

1// Copyright 2025 Neuraville Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4// HTTP server implementation (Axum)
5//
6// This module sets up the HTTP API server with Axum, including routing,
7// middleware, and state management.
8
9use axum::{
10    body::Body,
11    extract::State,
12    http::{Request, StatusCode},
13    middleware::{self, Next},
14    response::{Html, IntoResponse, Json, Redirect, Response},
15    routing::{get, put},
16    Router,
17};
18use http_body_util::BodyExt;
19use std::sync::Arc;
20use tower_http::{
21    cors::{Any, CorsLayer},
22    trace::TraceLayer,
23};
24use utoipa::OpenApi;
25
26#[cfg(feature = "http")]
27use crate::openapi::ApiDoc;
28#[cfg(feature = "services")]
29use feagi_services::traits::{AgentService, SystemService};
30#[cfg(feature = "services")]
31use feagi_services::{
32    AnalyticsService, ConnectomeService, GenomeService, NeuronService, RuntimeService,
33};
34
35/// Application state shared across all HTTP handlers
36#[derive(Clone)]
37pub struct ApiState {
38    pub agent_service: Option<Arc<dyn AgentService + Send + Sync>>,
39    pub analytics_service: Arc<dyn AnalyticsService + Send + Sync>,
40    pub connectome_service: Arc<dyn ConnectomeService + Send + Sync>,
41    pub genome_service: Arc<dyn GenomeService + Send + Sync>,
42    pub neuron_service: Arc<dyn NeuronService + Send + Sync>,
43    pub runtime_service: Arc<dyn RuntimeService + Send + Sync>,
44    pub system_service: Arc<dyn SystemService + Send + Sync>,
45    pub snapshot_service: Option<Arc<dyn feagi_services::SnapshotService + Send + Sync>>,
46    /// FEAGI session timestamp in milliseconds (Unix timestamp when FEAGI started)
47    /// This is a unique identifier for each FEAGI instance/session
48    pub feagi_session_timestamp: i64,
49}
50
51/// Create the main HTTP server application
52pub fn create_http_server(state: ApiState) -> Router {
53    Router::new()
54        // Root redirect to custom Swagger UI
55        .route("/", get(root_redirect))
56
57        // Custom Swagger UI with FEAGI branding at /swagger-ui/
58        .route("/swagger-ui/", get(custom_swagger_ui))
59
60        // OpenAPI spec endpoint
61        .route("/api-docs/openapi.json", get(|| async {
62            Json(ApiDoc::openapi())
63        }))
64
65        // Python-compatible paths: /v1/* (ONLY this, matching Python exactly)
66        .nest("/v1", create_v1_router())
67
68        // Catch-all route for debugging unmatched requests
69        .fallback(|| async {
70            tracing::warn!(target: "feagi-api", "Unmatched request - 404 Not Found");
71            (StatusCode::NOT_FOUND, "404 Not Found")
72        })
73
74        // Add state
75        .with_state(state)
76
77        // Add middleware
78        .layer(middleware::from_fn(log_request_response_bodies))
79        .layer(create_cors_layer())
80        .layer(
81            TraceLayer::new_for_http()
82                .make_span_with(|request: &axum::http::Request<_>| {
83                    tracing::span!(
84                        target: "feagi-api",
85                        tracing::Level::TRACE,
86                        "request",
87                        method = %request.method(),
88                        uri = %request.uri(),
89                        version = ?request.version(),
90                    )
91                })
92                .on_request(|request: &axum::http::Request<_>, _span: &tracing::Span| {
93                    tracing::trace!(target: "feagi-api", "Incoming request: {} {}", request.method(), request.uri());
94                })
95                .on_response(|response: &axum::http::Response<_>, latency: std::time::Duration, span: &tracing::Span| {
96                    tracing::trace!(
97                        target: "feagi-api",
98                        "Response: status={}, latency={:?}",
99                        response.status(),
100                        latency
101                    );
102                    span.record("status", response.status().as_u16());
103                    span.record("latency_ms", latency.as_millis());
104                })
105                .on_body_chunk(|chunk: &axum::body::Bytes, latency: std::time::Duration, _span: &tracing::Span| {
106                    tracing::trace!(target: "feagi-api", "Response chunk: {} bytes, latency={:?}", chunk.len(), latency);
107                })
108                .on_eos(|_trailers: Option<&axum::http::HeaderMap>, stream_duration: std::time::Duration, _span: &tracing::Span| {
109                    tracing::trace!(target: "feagi-api", "Stream ended, duration={:?}", stream_duration);
110                })
111                .on_failure(|_error: tower_http::classify::ServerErrorsFailureClass, latency: std::time::Duration, _span: &tracing::Span| {
112                    tracing::error!(target: "feagi-api", "Request failed, latency={:?}", latency);
113                })
114        )
115}
116
117/// Create V1 API router - Match Python structure EXACTLY
118/// Format: /v1/{module}/{snake_case_endpoint}
119fn create_v1_router() -> Router<ApiState> {
120    use crate::endpoints::agent::*; // Import agent functions for routes
121    use crate::endpoints::burst_engine;
122    use crate::endpoints::connectome;
123    use crate::endpoints::cortical_area;
124    use crate::endpoints::cortical_mapping;
125    use crate::endpoints::evolution;
126    use crate::endpoints::genome;
127    use crate::endpoints::input;
128    use crate::endpoints::insight;
129    use crate::endpoints::monitoring;
130    use crate::endpoints::morphology;
131    use crate::endpoints::network;
132    use crate::endpoints::neuroplasticity;
133    use crate::endpoints::outputs;
134    use crate::endpoints::physiology;
135    use crate::endpoints::region;
136    use crate::endpoints::simulation;
137    use crate::endpoints::training;
138    use crate::endpoints::visualization;
139    use crate::endpoints::{agent, system};
140
141    Router::new()
142        // ===== AGENT MODULE (12 endpoints) =====
143        .route("/agent/register", axum::routing::post(register_agent))
144        .route("/agent/heartbeat", axum::routing::post(heartbeat))
145        .route("/agent/list", get(list_agents))
146        .route("/agent/properties", get(get_agent_properties))
147        .route(
148            "/agent/properties/:agent_id",
149            get(agent::get_agent_properties_path),
150        )
151        .route("/agent/shared_mem", get(get_shared_memory))
152        .route("/agent/deregister", axum::routing::delete(deregister_agent))
153        .route(
154            "/agent/manual_stimulation",
155            axum::routing::post(manual_stimulation),
156        )
157        .route(
158            "/agent/fq_sampler_status",
159            get(agent::get_fq_sampler_status),
160        )
161        .route("/agent/capabilities", get(agent::get_capabilities))
162        .route("/agent/info/:agent_id", get(agent::get_agent_info))
163        .route(
164            "/agent/configure",
165            axum::routing::post(agent::post_configure),
166        )
167        // ===== SYSTEM MODULE (21 endpoints) =====
168        .route("/system/health_check", get(system::get_health_check))
169        .route(
170            "/system/cortical_area_visualization_skip_rate",
171            get(system::get_cortical_area_visualization_skip_rate)
172                .put(system::set_cortical_area_visualization_skip_rate),
173        )
174        .route(
175            "/system/cortical_area_visualization_suppression_threshold",
176            get(system::get_cortical_area_visualization_suppression_threshold)
177                .put(system::set_cortical_area_visualization_suppression_threshold),
178        )
179        .route("/system/version", get(system::get_version))
180        .route("/system/versions", get(system::get_versions))
181        .route("/system/configuration", get(system::get_configuration))
182        .route(
183            "/system/user_preferences",
184            get(system::get_user_preferences).put(system::put_user_preferences),
185        )
186        .route(
187            "/system/cortical_area_types",
188            get(system::get_cortical_area_types_list),
189        )
190        .route(
191            "/system/enable_visualization_fq_sampler",
192            axum::routing::post(system::post_enable_visualization_fq_sampler),
193        )
194        .route(
195            "/system/disable_visualization_fq_sampler",
196            axum::routing::post(system::post_disable_visualization_fq_sampler),
197        )
198        .route("/system/fcl_status", get(system::get_fcl_status_system))
199        .route(
200            "/system/fcl_reset",
201            axum::routing::post(system::post_fcl_reset_system),
202        )
203        .route("/system/processes", get(system::get_processes))
204        .route("/system/unique_logs", get(system::get_unique_logs))
205        .route("/system/logs", axum::routing::post(system::post_logs))
206        .route(
207            "/system/beacon/subscribers",
208            get(system::get_beacon_subscribers),
209        )
210        .route(
211            "/system/beacon/subscribe",
212            axum::routing::post(system::post_beacon_subscribe),
213        )
214        .route(
215            "/system/beacon/unsubscribe",
216            axum::routing::delete(system::delete_beacon_unsubscribe),
217        )
218        .route(
219            "/system/global_activity_visualization",
220            get(system::get_global_activity_visualization)
221                .put(system::put_global_activity_visualization),
222        )
223        .route(
224            "/system/circuit_library_path",
225            axum::routing::post(system::post_circuit_library_path),
226        )
227        .route("/system/db/influxdb/test", get(system::get_influxdb_test))
228        .route(
229            "/system/register",
230            axum::routing::post(system::post_register_system),
231        )
232        // ===== CORTICAL_AREA MODULE (25 endpoints) =====
233        .route("/cortical_area/ipu", get(cortical_area::get_ipu))
234        .route(
235            "/cortical_area/ipu/types",
236            get(cortical_area::get_ipu_types),
237        )
238        .route("/cortical_area/opu", get(cortical_area::get_opu))
239        .route(
240            "/cortical_area/opu/types",
241            get(cortical_area::get_opu_types),
242        )
243        .route(
244            "/cortical_area/cortical_area_id_list",
245            get(cortical_area::get_cortical_area_id_list),
246        )
247        .route(
248            "/cortical_area/cortical_area_name_list",
249            get(cortical_area::get_cortical_area_name_list),
250        )
251        .route(
252            "/cortical_area/cortical_id_name_mapping",
253            get(cortical_area::get_cortical_id_name_mapping),
254        )
255        .route(
256            "/cortical_area/cortical_types",
257            get(cortical_area::get_cortical_types),
258        )
259        .route(
260            "/cortical_area/cortical_map_detailed",
261            get(cortical_area::get_cortical_map_detailed),
262        )
263        .route(
264            "/cortical_area/cortical_locations_2d",
265            get(cortical_area::get_cortical_locations_2d),
266        )
267        .route(
268            "/cortical_area/cortical_area/geometry",
269            get(cortical_area::get_cortical_area_geometry),
270        )
271        .route(
272            "/cortical_area/cortical_visibility",
273            get(cortical_area::get_cortical_visibility),
274        )
275        .route(
276            "/cortical_area/cortical_name_location",
277            axum::routing::post(cortical_area::post_cortical_name_location),
278        )
279        .route(
280            "/cortical_area/cortical_area_properties",
281            axum::routing::post(cortical_area::post_cortical_area_properties),
282        )
283        .route(
284            "/cortical_area/multi/cortical_area_properties",
285            axum::routing::post(cortical_area::post_multi_cortical_area_properties),
286        )
287        .route(
288            "/cortical_area/cortical_area",
289            axum::routing::post(cortical_area::post_cortical_area)
290                .put(cortical_area::put_cortical_area)
291                .delete(cortical_area::delete_cortical_area),
292        )
293        .route(
294            "/cortical_area/custom_cortical_area",
295            axum::routing::post(cortical_area::post_custom_cortical_area),
296        )
297        .route(
298            "/cortical_area/clone",
299            axum::routing::post(cortical_area::post_clone),
300        )
301        .route(
302            "/cortical_area/multi/cortical_area",
303            put(cortical_area::put_multi_cortical_area)
304                .delete(cortical_area::delete_multi_cortical_area),
305        )
306        .route("/cortical_area/coord_2d", put(cortical_area::put_coord_2d))
307        .route(
308            "/cortical_area/suppress_cortical_visibility",
309            put(cortical_area::put_suppress_cortical_visibility),
310        )
311        .route("/cortical_area/reset", put(cortical_area::put_reset))
312        .route(
313            "/cortical_area/visualization",
314            get(cortical_area::get_visualization),
315        )
316        .route(
317            "/cortical_area/batch_operations",
318            axum::routing::post(cortical_area::post_batch_operations),
319        )
320        .route("/cortical_area/ipu/list", get(cortical_area::get_ipu_list))
321        .route("/cortical_area/opu/list", get(cortical_area::get_opu_list))
322        .route(
323            "/cortical_area/coordinates_3d",
324            put(cortical_area::put_coordinates_3d),
325        )
326        .route(
327            "/cortical_area/bulk_delete",
328            axum::routing::delete(cortical_area::delete_bulk),
329        )
330        .route(
331            "/cortical_area/resize",
332            axum::routing::post(cortical_area::post_resize),
333        )
334        .route(
335            "/cortical_area/reposition",
336            axum::routing::post(cortical_area::post_reposition),
337        )
338        .route(
339            "/cortical_area/voxel_neurons",
340            axum::routing::post(cortical_area::post_voxel_neurons),
341        )
342        .route(
343            "/cortical_area/cortical_area_index_list",
344            get(cortical_area::get_cortical_area_index_list),
345        )
346        .route(
347            "/cortical_area/cortical_idx_mapping",
348            get(cortical_area::get_cortical_idx_mapping),
349        )
350        .route(
351            "/cortical_area/mapping_restrictions",
352            get(cortical_area::get_mapping_restrictions_query)
353                .post(cortical_area::post_mapping_restrictions),
354        )
355        .route(
356            "/cortical_area/:cortical_id/memory_usage",
357            get(cortical_area::get_memory_usage),
358        )
359        .route(
360            "/cortical_area/:cortical_id/neuron_count",
361            get(cortical_area::get_area_neuron_count),
362        )
363        .route(
364            "/cortical_area/cortical_type_options",
365            axum::routing::post(cortical_area::post_cortical_type_options),
366        )
367        .route(
368            "/cortical_area/mapping_restrictions_between_areas",
369            axum::routing::post(cortical_area::post_mapping_restrictions_between_areas),
370        )
371        .route("/cortical_area/coord_3d", put(cortical_area::put_coord_3d))
372        // ===== MORPHOLOGY MODULE (14 endpoints) =====
373        .route(
374            "/morphology/morphology_list",
375            get(morphology::get_morphology_list),
376        )
377        .route(
378            "/morphology/morphology_types",
379            get(morphology::get_morphology_types),
380        )
381        .route("/morphology/list/types", get(morphology::get_list_types))
382        .route(
383            "/morphology/morphologies",
384            get(morphology::get_morphologies),
385        )
386        .route(
387            "/morphology/morphology",
388            axum::routing::post(morphology::post_morphology)
389                .put(morphology::put_morphology)
390                .delete(morphology::delete_morphology_by_name),
391        )
392        .route(
393            "/morphology/morphology_properties",
394            axum::routing::post(morphology::post_morphology_properties),
395        )
396        .route(
397            "/morphology/morphology_usage",
398            axum::routing::post(morphology::post_morphology_usage),
399        )
400        .route("/morphology/list", get(morphology::get_list))
401        .route("/morphology/info/:morphology_id", get(morphology::get_info))
402        .route(
403            "/morphology/create",
404            axum::routing::post(morphology::post_create),
405        )
406        .route(
407            "/morphology/update",
408            axum::routing::put(morphology::put_update),
409        )
410        .route(
411            "/morphology/delete/:morphology_id",
412            axum::routing::delete(morphology::delete_morphology),
413        )
414        // ===== REGION MODULE (12 endpoints) =====
415        .route("/region/regions_members", get(region::get_regions_members))
416        .route(
417            "/region/region",
418            axum::routing::post(region::post_region)
419                .put(region::put_region)
420                .delete(region::delete_region),
421        )
422        .route("/region/clone", axum::routing::post(region::post_clone))
423        .route(
424            "/region/relocate_members",
425            put(region::put_relocate_members),
426        )
427        .route(
428            "/region/region_and_members",
429            axum::routing::delete(region::delete_region_and_members),
430        )
431        .route("/region/regions", get(region::get_regions))
432        .route("/region/region_titles", get(region::get_region_titles))
433        .route("/region/region/:region_id", get(region::get_region_detail))
434        .route(
435            "/region/change_region_parent",
436            put(region::put_change_region_parent),
437        )
438        .route(
439            "/region/change_cortical_area_region",
440            put(region::put_change_cortical_area_region),
441        )
442        // ===== CORTICAL_MAPPING MODULE (8 endpoints) =====
443        .route(
444            "/cortical_mapping/afferents",
445            axum::routing::post(cortical_mapping::post_afferents),
446        )
447        .route(
448            "/cortical_mapping/efferents",
449            axum::routing::post(cortical_mapping::post_efferents),
450        )
451        .route(
452            "/cortical_mapping/mapping_properties",
453            axum::routing::post(cortical_mapping::post_mapping_properties)
454                .put(cortical_mapping::put_mapping_properties),
455        )
456        .route(
457            "/cortical_mapping/mapping",
458            get(cortical_mapping::get_mapping).delete(cortical_mapping::delete_mapping),
459        )
460        .route(
461            "/cortical_mapping/mapping_list",
462            get(cortical_mapping::get_mapping_list),
463        )
464        .route(
465            "/cortical_mapping/batch_update",
466            axum::routing::post(cortical_mapping::post_batch_update),
467        )
468        .route(
469            "/cortical_mapping/mapping",
470            axum::routing::post(cortical_mapping::post_mapping).put(cortical_mapping::put_mapping),
471        )
472        // ===== CONNECTOME MODULE (21 endpoints) =====
473        .route(
474            "/connectome/cortical_areas/list/detailed",
475            get(connectome::get_cortical_areas_list_detailed),
476        )
477        .route(
478            "/connectome/properties/dimensions",
479            get(connectome::get_properties_dimensions),
480        )
481        .route(
482            "/connectome/properties/mappings",
483            get(connectome::get_properties_mappings),
484        )
485        .route("/connectome/snapshot", get(connectome::get_snapshot))
486        .route("/connectome/stats", get(connectome::get_stats))
487        .route(
488            "/connectome/batch_neuron_operations",
489            axum::routing::post(connectome::post_batch_neuron_operations),
490        )
491        .route(
492            "/connectome/batch_synapse_operations",
493            axum::routing::post(connectome::post_batch_synapse_operations),
494        )
495        .route(
496            "/connectome/neuron_count",
497            get(connectome::get_neuron_count),
498        )
499        .route(
500            "/connectome/synapse_count",
501            get(connectome::get_synapse_count),
502        )
503        .route("/connectome/paths", get(connectome::get_paths))
504        .route(
505            "/connectome/cumulative_stats",
506            get(connectome::get_cumulative_stats),
507        )
508        .route(
509            "/connectome/area_details",
510            get(connectome::get_area_details),
511        )
512        .route(
513            "/connectome/rebuild",
514            axum::routing::post(connectome::post_rebuild),
515        )
516        .route("/connectome/structure", get(connectome::get_structure))
517        .route(
518            "/connectome/clear",
519            axum::routing::post(connectome::post_clear),
520        )
521        .route("/connectome/validation", get(connectome::get_validation))
522        .route("/connectome/topology", get(connectome::get_topology))
523        .route(
524            "/connectome/optimize",
525            axum::routing::post(connectome::post_optimize),
526        )
527        .route(
528            "/connectome/connectivity_matrix",
529            get(connectome::get_connectivity_matrix),
530        )
531        .route(
532            "/connectome/neurons/batch",
533            axum::routing::post(connectome::post_neurons_batch),
534        )
535        .route(
536            "/connectome/synapses/batch",
537            axum::routing::post(connectome::post_synapses_batch),
538        )
539        .route(
540            "/connectome/cortical_areas/list/summary",
541            get(connectome::get_cortical_areas_list_summary),
542        )
543        .route(
544            "/connectome/cortical_areas/list/transforming",
545            get(connectome::get_cortical_areas_list_transforming),
546        )
547        .route(
548            "/connectome/cortical_area/list/types",
549            get(connectome::get_cortical_area_list_types),
550        )
551        .route(
552            "/connectome/cortical_area/:cortical_id/neurons",
553            get(connectome::get_cortical_area_neurons),
554        )
555        .route(
556            "/connectome/:cortical_area_id/synapses",
557            get(connectome::get_area_synapses),
558        )
559        .route(
560            "/connectome/cortical_info/:cortical_area",
561            get(connectome::get_cortical_info),
562        )
563        .route(
564            "/connectome/stats/cortical/cumulative/:cortical_area",
565            get(connectome::get_stats_cortical_cumulative),
566        )
567        .route(
568            "/connectome/neuron/:neuron_id/properties",
569            get(connectome::get_neuron_properties_by_id),
570        )
571        .route(
572            "/connectome/neuron_properties",
573            get(connectome::get_neuron_properties_query),
574        )
575        .route(
576            "/connectome/area_neurons",
577            get(connectome::get_area_neurons_query),
578        )
579        .route(
580            "/connectome/fire_queue/:cortical_area",
581            get(connectome::get_fire_queue_area),
582        )
583        .route(
584            "/connectome/plasticity",
585            get(connectome::get_plasticity_info),
586        )
587        .route("/connectome/path", get(connectome::get_path_query))
588        .route(
589            "/connectome/download",
590            get(connectome::get_download_connectome),
591        )
592        .route(
593            "/connectome/download-cortical-area/:cortical_area",
594            get(connectome::get_download_cortical_area),
595        )
596        .route(
597            "/connectome/upload",
598            axum::routing::post(connectome::post_upload_connectome),
599        )
600        .route(
601            "/connectome/upload-cortical-area",
602            axum::routing::post(connectome::post_upload_cortical_area),
603        )
604        // ===== BURST_ENGINE MODULE (14 endpoints) =====
605        .route(
606            "/burst_engine/simulation_timestep",
607            get(burst_engine::get_simulation_timestep).post(burst_engine::post_simulation_timestep),
608        )
609        .route("/burst_engine/fcl", get(burst_engine::get_fcl))
610        .route(
611            "/burst_engine/fire_queue",
612            get(burst_engine::get_fire_queue),
613        )
614        .route(
615            "/burst_engine/fcl_reset",
616            axum::routing::post(burst_engine::post_fcl_reset),
617        )
618        .route(
619            "/burst_engine/fcl_status",
620            get(burst_engine::get_fcl_status),
621        )
622        .route(
623            "/burst_engine/fcl_sampler/config",
624            get(burst_engine::get_fcl_sampler_config).post(burst_engine::post_fcl_sampler_config),
625        )
626        .route(
627            "/burst_engine/fcl_sampler/area/:area_id/sample_rate",
628            get(burst_engine::get_area_fcl_sample_rate)
629                .post(burst_engine::post_area_fcl_sample_rate),
630        )
631        .route(
632            "/burst_engine/fire_ledger/default_window_size",
633            get(burst_engine::get_fire_ledger_default_window_size)
634                .put(burst_engine::put_fire_ledger_default_window_size),
635        )
636        .route(
637            "/burst_engine/fire_ledger/areas_window_config",
638            get(burst_engine::get_fire_ledger_areas_window_config),
639        )
640        .route("/burst_engine/stats", get(burst_engine::get_stats))
641        .route("/burst_engine/status", get(burst_engine::get_status))
642        .route(
643            "/burst_engine/control",
644            axum::routing::post(burst_engine::post_control),
645        )
646        .route(
647            "/burst_engine/burst_counter",
648            get(burst_engine::get_burst_counter),
649        )
650        .route(
651            "/burst_engine/start",
652            axum::routing::post(burst_engine::post_start),
653        )
654        .route(
655            "/burst_engine/stop",
656            axum::routing::post(burst_engine::post_stop),
657        )
658        .route(
659            "/burst_engine/hold",
660            axum::routing::post(burst_engine::post_hold),
661        )
662        .route(
663            "/burst_engine/resume",
664            axum::routing::post(burst_engine::post_resume),
665        )
666        .route(
667            "/burst_engine/config",
668            get(burst_engine::get_config).put(burst_engine::put_config),
669        )
670        .route(
671            "/burst_engine/fire_ledger/area/:area_id/window_size",
672            get(burst_engine::get_fire_ledger_area_window_size)
673                .put(burst_engine::put_fire_ledger_area_window_size),
674        )
675        .route(
676            "/burst_engine/fire_ledger/area/:area_id/history",
677            get(burst_engine::get_fire_ledger_history),
678        )
679        .route(
680            "/burst_engine/membrane_potentials",
681            get(burst_engine::get_membrane_potentials).put(burst_engine::put_membrane_potentials),
682        )
683        .route(
684            "/burst_engine/frequency_status",
685            get(burst_engine::get_frequency_status),
686        )
687        .route(
688            "/burst_engine/measure_frequency",
689            axum::routing::post(burst_engine::post_measure_frequency),
690        )
691        .route(
692            "/burst_engine/frequency_history",
693            get(burst_engine::get_frequency_history),
694        )
695        .route(
696            "/burst_engine/force_connectome_integration",
697            axum::routing::post(burst_engine::post_force_connectome_integration),
698        )
699        // ===== GENOME MODULE (22 endpoints) =====
700        .route("/genome/file_name", get(genome::get_file_name))
701        .route("/genome/circuits", get(genome::get_circuits))
702        .route(
703            "/genome/amalgamation_destination",
704            axum::routing::post(genome::post_amalgamation_destination),
705        )
706        .route(
707            "/genome/amalgamation_cancellation",
708            axum::routing::delete(genome::delete_amalgamation_cancellation),
709        )
710        .route(
711            "/feagi/genome/append",
712            axum::routing::post(genome::post_genome_append),
713        )
714        .route(
715            "/genome/upload/barebones",
716            axum::routing::post(genome::post_upload_barebones_genome),
717        )
718        .route(
719            "/genome/upload/essential",
720            axum::routing::post(genome::post_upload_essential_genome),
721        )
722        .route("/genome/name", get(genome::get_name))
723        .route("/genome/timestamp", get(genome::get_timestamp))
724        .route("/genome/save", axum::routing::post(genome::post_save))
725        .route("/genome/load", axum::routing::post(genome::post_load))
726        .route("/genome/upload", axum::routing::post(genome::post_upload))
727        .route("/genome/download", get(genome::get_download))
728        .route("/genome/properties", get(genome::get_properties))
729        .route(
730            "/genome/validate",
731            axum::routing::post(genome::post_validate),
732        )
733        .route(
734            "/genome/transform",
735            axum::routing::post(genome::post_transform),
736        )
737        .route("/genome/clone", axum::routing::post(genome::post_clone))
738        .route("/genome/reset", axum::routing::post(genome::post_reset))
739        .route("/genome/metadata", get(genome::get_metadata))
740        .route("/genome/merge", axum::routing::post(genome::post_merge))
741        .route("/genome/diff", get(genome::get_diff))
742        .route(
743            "/genome/export_format",
744            axum::routing::post(genome::post_export_format),
745        )
746        .route("/genome/amalgamation", get(genome::get_amalgamation))
747        .route(
748            "/genome/amalgamation_history",
749            get(genome::get_amalgamation_history_exact),
750        )
751        .route(
752            "/genome/cortical_template",
753            get(genome::get_cortical_template),
754        )
755        .route("/genome/defaults/files", get(genome::get_defaults_files))
756        .route("/genome/download_region", get(genome::get_download_region))
757        .route("/genome/genome_number", get(genome::get_genome_number))
758        .route(
759            "/genome/amalgamation_by_filename",
760            axum::routing::post(genome::post_amalgamation_by_filename),
761        )
762        .route(
763            "/genome/amalgamation_by_payload",
764            axum::routing::post(genome::post_amalgamation_by_payload),
765        )
766        .route(
767            "/genome/amalgamation_by_upload",
768            axum::routing::post(genome::post_amalgamation_by_upload),
769        )
770        .route(
771            "/genome/append-file",
772            axum::routing::post(genome::post_append_file),
773        )
774        .route(
775            "/genome/upload/file",
776            axum::routing::post(genome::post_upload_file),
777        )
778        .route(
779            "/genome/upload/file/edit",
780            axum::routing::post(genome::post_upload_file_edit),
781        )
782        .route(
783            "/genome/upload/string",
784            axum::routing::post(genome::post_upload_string),
785        )
786        // ===== NEUROPLASTICITY MODULE (7 endpoints) =====
787        .route(
788            "/neuroplasticity/plasticity_queue_depth",
789            get(neuroplasticity::get_plasticity_queue_depth)
790                .put(neuroplasticity::put_plasticity_queue_depth),
791        )
792        .route("/neuroplasticity/status", get(neuroplasticity::get_status))
793        .route(
794            "/neuroplasticity/transforming",
795            get(neuroplasticity::get_transforming),
796        )
797        .route(
798            "/neuroplasticity/configure",
799            axum::routing::post(neuroplasticity::post_configure),
800        )
801        .route(
802            "/neuroplasticity/enable/:area_id",
803            axum::routing::post(neuroplasticity::post_enable_area),
804        )
805        .route(
806            "/neuroplasticity/disable/:area_id",
807            axum::routing::post(neuroplasticity::post_disable_area),
808        )
809        // ===== INSIGHT MODULE (6 endpoints) =====
810        .route(
811            "/insight/neurons/membrane_potential_status",
812            axum::routing::post(insight::post_neurons_membrane_potential_status),
813        )
814        .route(
815            "/insight/neuron/synaptic_potential_status",
816            axum::routing::post(insight::post_neuron_synaptic_potential_status),
817        )
818        .route(
819            "/insight/neurons/membrane_potential_set",
820            axum::routing::post(insight::post_neurons_membrane_potential_set),
821        )
822        .route(
823            "/insight/neuron/synaptic_potential_set",
824            axum::routing::post(insight::post_neuron_synaptic_potential_set),
825        )
826        .route("/insight/analytics", get(insight::get_analytics))
827        .route("/insight/data", get(insight::get_data))
828        // ===== INPUT MODULE (4 endpoints) =====
829        .route(
830            "/input/vision",
831            get(input::get_vision).post(input::post_vision),
832        )
833        .route("/input/sources", get(input::get_sources))
834        .route(
835            "/input/configure",
836            axum::routing::post(input::post_configure),
837        )
838        // ===== OUTPUTS MODULE (2 endpoints) - Python uses /v1/output (singular)
839        .route("/output/targets", get(outputs::get_targets))
840        .route(
841            "/output/configure",
842            axum::routing::post(outputs::post_configure),
843        )
844        // ===== PHYSIOLOGY MODULE (2 endpoints) =====
845        .route(
846            "/physiology/",
847            get(physiology::get_physiology).put(physiology::put_physiology),
848        )
849        // ===== SIMULATION MODULE (6 endpoints) =====
850        .route(
851            "/simulation/upload/string",
852            axum::routing::post(simulation::post_stimulation_upload),
853        )
854        .route(
855            "/simulation/reset",
856            axum::routing::post(simulation::post_reset),
857        )
858        .route("/simulation/status", get(simulation::get_status))
859        .route("/simulation/stats", get(simulation::get_stats))
860        .route(
861            "/simulation/config",
862            axum::routing::post(simulation::post_config),
863        )
864        // ===== TRAINING MODULE (25 endpoints) =====
865        .route("/training/shock", axum::routing::post(training::post_shock))
866        .route("/training/shock/options", get(training::get_shock_options))
867        .route("/training/shock/status", get(training::get_shock_status))
868        .route(
869            "/training/shock/activate",
870            axum::routing::post(training::post_shock_activate),
871        )
872        .route(
873            "/training/reward/intensity",
874            axum::routing::post(training::post_reward_intensity),
875        )
876        .route(
877            "/training/reward",
878            axum::routing::post(training::post_reward),
879        )
880        .route(
881            "/training/punishment/intensity",
882            axum::routing::post(training::post_punishment_intensity),
883        )
884        .route(
885            "/training/punishment",
886            axum::routing::post(training::post_punishment),
887        )
888        .route(
889            "/training/gameover",
890            axum::routing::post(training::post_gameover),
891        )
892        .route("/training/brain_fitness", get(training::get_brain_fitness))
893        .route(
894            "/training/fitness_criteria",
895            get(training::get_fitness_criteria)
896                .put(training::put_fitness_criteria)
897                .post(training::post_fitness_criteria),
898        )
899        .route(
900            "/training/fitness_stats",
901            get(training::get_fitness_stats)
902                .put(training::put_fitness_stats)
903                .delete(training::delete_fitness_stats),
904        )
905        .route(
906            "/training/reset_fitness_stats",
907            axum::routing::delete(training::delete_reset_fitness_stats),
908        )
909        .route(
910            "/training/training_report",
911            get(training::get_training_report),
912        )
913        .route("/training/status", get(training::get_status))
914        .route("/training/stats", get(training::get_stats))
915        .route(
916            "/training/config",
917            axum::routing::post(training::post_config),
918        )
919        // ===== VISUALIZATION MODULE (4 endpoints) =====
920        .route(
921            "/visualization/register_client",
922            axum::routing::post(visualization::post_register_client),
923        )
924        .route(
925            "/visualization/unregister_client",
926            axum::routing::post(visualization::post_unregister_client),
927        )
928        .route(
929            "/visualization/heartbeat",
930            axum::routing::post(visualization::post_heartbeat),
931        )
932        .route("/visualization/status", get(visualization::get_status))
933        // ===== MONITORING MODULE (4 endpoints) =====
934        .route("/monitoring/status", get(monitoring::get_status))
935        .route("/monitoring/metrics", get(monitoring::get_metrics))
936        .route("/monitoring/data", get(monitoring::get_data))
937        .route("/monitoring/performance", get(monitoring::get_performance))
938        // ===== EVOLUTION MODULE (3 endpoints) =====
939        .route("/evolution/status", get(evolution::get_status))
940        .route(
941            "/evolution/config",
942            axum::routing::post(evolution::post_config),
943        )
944        // ===== SNAPSHOT MODULE (12 endpoints) =====
945        // TODO: Implement snapshot endpoints
946        // .route("/snapshot/create", axum::routing::post(snapshot::post_create))
947        // .route("/snapshot/restore", axum::routing::post(snapshot::post_restore))
948        // .route("/snapshot/", get(snapshot::get_list))
949        // .route("/snapshot/:snapshot_id", axum::routing::delete(snapshot::delete_snapshot))
950        // .route("/snapshot/:snapshot_id/artifact/:fmt", get(snapshot::get_artifact))
951        // .route("/snapshot/compare", axum::routing::post(snapshot::post_compare))
952        // .route("/snapshot/upload", axum::routing::post(snapshot::post_upload))
953        // // Python uses /v1/snapshots/* (note the S)
954        // .route("/snapshots/connectome", axum::routing::post(snapshot::post_snapshots_connectome))
955        // .route("/snapshots/connectome/:snapshot_id/restore", axum::routing::post(snapshot::post_snapshots_connectome_restore))
956        // .route("/snapshots/:snapshot_id/restore", axum::routing::post(snapshot::post_snapshots_restore))
957        // .route("/snapshots/:snapshot_id", axum::routing::delete(snapshot::delete_snapshots_by_id))
958        // .route("/snapshots/:snapshot_id/artifact/:fmt", get(snapshot::get_snapshots_artifact))
959        // ===== NETWORK MODULE (3 endpoints) =====
960        .route("/network/status", get(network::get_status))
961        .route("/network/config", axum::routing::post(network::post_config))
962}
963
964/// OpenAPI spec handler
965#[allow(dead_code)] // In development - will be wired to OpenAPI route
966async fn openapi_spec() -> Json<utoipa::openapi::OpenApi> {
967    Json(ApiDoc::openapi())
968}
969
970// ============================================================================
971// CORS CONFIGURATION
972// ============================================================================
973
974/// Create CORS layer for the API
975///
976/// TODO: Configure for production:
977/// - Restrict allowed origins
978/// - Allowed methods restricted
979/// - Credentials support as needed
980fn create_cors_layer() -> CorsLayer {
981    CorsLayer::new()
982        .allow_origin(Any)
983        .allow_methods(Any)
984        .allow_headers(Any)
985}
986
987/// Middleware to log request and response bodies for debugging
988async fn log_request_response_bodies(
989    request: Request<Body>,
990    next: Next,
991) -> Result<Response, StatusCode> {
992    let (parts, body) = request.into_parts();
993
994    // Only log bodies for POST/PUT/PATCH requests
995    let should_log_request = matches!(parts.method.as_str(), "POST" | "PUT" | "PATCH");
996
997    let body_bytes = if should_log_request {
998        // Collect body bytes
999        match body.collect().await {
1000            Ok(collected) => {
1001                let bytes = collected.to_bytes();
1002                // Log request body if it's JSON
1003                if let Ok(body_str) = String::from_utf8(bytes.to_vec()) {
1004                    if !body_str.is_empty() {
1005                        tracing::trace!(target: "feagi-api", "Request body: {}", body_str);
1006                    }
1007                }
1008                bytes
1009            }
1010            Err(_) => {
1011                return Err(StatusCode::INTERNAL_SERVER_ERROR);
1012            }
1013        }
1014    } else {
1015        axum::body::Bytes::new()
1016    };
1017
1018    // Reconstruct request with original body
1019    let request = Request::from_parts(parts, Body::from(body_bytes));
1020
1021    // Call the next handler
1022    let response = next.run(request).await;
1023
1024    // Log response body
1025    let (parts, body) = response.into_parts();
1026
1027    match body.collect().await {
1028        Ok(collected) => {
1029            let bytes = collected.to_bytes();
1030            // Log response body if it's JSON and not too large
1031            if bytes.len() < 10000 {
1032                // Only log responses < 10KB
1033                if let Ok(body_str) = String::from_utf8(bytes.to_vec()) {
1034                    if !body_str.is_empty() && body_str.starts_with('{') {
1035                        tracing::trace!(target: "feagi-api", "Response body: {}", body_str);
1036                    }
1037                }
1038            }
1039            // Reconstruct response
1040            Ok(Response::from_parts(parts, Body::from(bytes)))
1041        }
1042        Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR),
1043    }
1044}
1045
1046// ============================================================================
1047// HELPER HANDLERS
1048// ============================================================================
1049
1050/// Root redirect handler - redirects to Swagger UI
1051async fn root_redirect() -> Redirect {
1052    Redirect::permanent("/swagger-ui/")
1053}
1054
1055// Custom Swagger UI with FEAGI branding and dark/light themes
1056// Embedded from templates/custom-swagger-ui.html at compile time
1057async fn custom_swagger_ui() -> Html<&'static str> {
1058    const CUSTOM_SWAGGER_HTML: &str = include_str!("../../../templates/custom-swagger-ui.html");
1059    Html(CUSTOM_SWAGGER_HTML)
1060}
1061
1062// ============================================================================
1063// PLACEHOLDER HANDLERS (for endpoints not yet implemented)
1064// ============================================================================
1065
1066/// Placeholder handler for unimplemented endpoints
1067/// Returns 501 Not Implemented with a clear message
1068#[allow(dead_code)] // In development - will be used for placeholder routes
1069async fn placeholder_handler(State(_state): State<ApiState>) -> Response {
1070    (
1071        StatusCode::NOT_IMPLEMENTED,
1072        Json(serde_json::json!({
1073            "error": "Not yet implemented",
1074            "message": "This endpoint is registered but not yet implemented in Rust. See Python implementation."
1075        }))
1076    ).into_response()
1077}
1078
1079/// Placeholder health check - returns basic response
1080#[allow(dead_code)] // In development - will be used for basic health route
1081async fn placeholder_health_check(State(_state): State<ApiState>) -> Response {
1082    (
1083        StatusCode::OK,
1084        Json(serde_json::json!({
1085            "status": "ok",
1086            "message": "Health check placeholder - Python-compatible path structure confirmed",
1087            "burst_engine": false,
1088            "brain_readiness": false
1089        })),
1090    )
1091        .into_response()
1092}