move repos into monorepo
This commit is contained in:
29
bin/osiris/Cargo.toml
Normal file
29
bin/osiris/Cargo.toml
Normal file
@@ -0,0 +1,29 @@
|
||||
[package]
|
||||
name = "osiris-server"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
description = "Osiris HTTP server"
|
||||
license = "MIT OR Apache-2.0"
|
||||
|
||||
[[bin]]
|
||||
name = "osiris"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
# Osiris core
|
||||
osiris-core = { path = "../../lib/osiris/core" }
|
||||
|
||||
# Web framework
|
||||
axum = "0.7"
|
||||
tower = "0.4"
|
||||
tower-http.workspace = true
|
||||
|
||||
# Core dependencies
|
||||
tokio.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
anyhow.workspace = true
|
||||
|
||||
# Tracing
|
||||
tracing.workspace = true
|
||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||
145
bin/osiris/src/main.rs
Normal file
145
bin/osiris/src/main.rs
Normal file
@@ -0,0 +1,145 @@
|
||||
//! Osiris Server - Generic OpenAPI REST server for Osiris data structures
|
||||
//!
|
||||
//! Provides generic CRUD operations for all Osiris structs via REST API.
|
||||
//! Routes follow pattern: GET /api/:struct_name/:id
|
||||
|
||||
use axum::{
|
||||
extract::{Path, Query, State},
|
||||
http::StatusCode,
|
||||
response::{IntoResponse, Json},
|
||||
routing::get,
|
||||
Router,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{json, Value};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use tower_http::cors::{Any, CorsLayer};
|
||||
use tracing::{info, warn};
|
||||
|
||||
#[derive(Clone)]
|
||||
struct AppState {
|
||||
// In a real implementation, this would be a Redis connection pool
|
||||
// For now, we'll use an in-memory store for demonstration
|
||||
store: Arc<tokio::sync::RwLock<HashMap<String, HashMap<String, Value>>>>,
|
||||
}
|
||||
|
||||
impl AppState {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
store: Arc::new(tokio::sync::RwLock::new(HashMap::new())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
// Initialize tracing
|
||||
tracing_subscriber::fmt()
|
||||
.with_target(false)
|
||||
.compact()
|
||||
.init();
|
||||
|
||||
let state = AppState::new();
|
||||
|
||||
// Build router
|
||||
let app = Router::new()
|
||||
.route("/health", get(health_check))
|
||||
.route("/api/:struct_name", get(list_structs))
|
||||
.route("/api/:struct_name/:id", get(get_struct))
|
||||
.layer(
|
||||
CorsLayer::new()
|
||||
.allow_origin(Any)
|
||||
.allow_methods(Any)
|
||||
.allow_headers(Any),
|
||||
)
|
||||
.with_state(state);
|
||||
|
||||
let addr = "0.0.0.0:8081";
|
||||
info!("🚀 Osiris Server starting on {}", addr);
|
||||
info!("📖 API Documentation: http://localhost:8081/health");
|
||||
|
||||
let listener = tokio::net::TcpListener::bind(addr)
|
||||
.await
|
||||
.expect("Failed to bind address");
|
||||
|
||||
axum::serve(listener, app)
|
||||
.await
|
||||
.expect("Server failed");
|
||||
}
|
||||
|
||||
/// Health check endpoint
|
||||
async fn health_check() -> impl IntoResponse {
|
||||
Json(json!({
|
||||
"status": "healthy",
|
||||
"service": "osiris-server",
|
||||
"version": "0.1.0"
|
||||
}))
|
||||
}
|
||||
|
||||
/// Generic GET endpoint for a single struct by ID
|
||||
/// GET /api/:struct_name/:id
|
||||
async fn get_struct(
|
||||
State(state): State<AppState>,
|
||||
Path((struct_name, id)): Path<(String, String)>,
|
||||
) -> Result<Json<Value>, (StatusCode, String)> {
|
||||
info!("GET /api/{}/{}", struct_name, id);
|
||||
|
||||
let store = state.store.read().await;
|
||||
|
||||
if let Some(struct_store) = store.get(&struct_name) {
|
||||
if let Some(data) = struct_store.get(&id) {
|
||||
return Ok(Json(data.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
warn!("Not found: {}/{}", struct_name, id);
|
||||
Err((
|
||||
StatusCode::NOT_FOUND,
|
||||
format!("{}/{} not found", struct_name, id),
|
||||
))
|
||||
}
|
||||
|
||||
/// Generic LIST endpoint for all instances of a struct
|
||||
/// GET /api/:struct_name?field=value
|
||||
async fn list_structs(
|
||||
State(state): State<AppState>,
|
||||
Path(struct_name): Path<String>,
|
||||
Query(params): Query<HashMap<String, String>>,
|
||||
) -> Result<Json<Vec<Value>>, (StatusCode, String)> {
|
||||
info!("GET /api/{} with params: {:?}", struct_name, params);
|
||||
|
||||
let store = state.store.read().await;
|
||||
|
||||
if let Some(struct_store) = store.get(&struct_name) {
|
||||
let mut results: Vec<Value> = struct_store.values().cloned().collect();
|
||||
|
||||
// Apply filters if any
|
||||
if !params.is_empty() {
|
||||
results.retain(|item| {
|
||||
params.iter().all(|(key, value)| {
|
||||
item.get(key)
|
||||
.and_then(|v| v.as_str())
|
||||
.map(|v| v == value)
|
||||
.unwrap_or(false)
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
return Ok(Json(results));
|
||||
}
|
||||
|
||||
// Return empty array if struct type doesn't exist yet
|
||||
Ok(Json(vec![]))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_health_check() {
|
||||
let response = health_check().await.into_response();
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user