feat: stub master file structures
This commit is contained in:
79
crates/nxmesh-master/Cargo.toml
Normal file
79
crates/nxmesh-master/Cargo.toml
Normal file
@@ -0,0 +1,79 @@
|
||||
[package]
|
||||
name = "nxmesh-master"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
authors.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
default-run = "nxmesh-master"
|
||||
|
||||
[[bin]]
|
||||
name = "nxmesh-master"
|
||||
path = "src/main.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "gen-openapi"
|
||||
path = "src/bin/gen-openapi.rs"
|
||||
|
||||
[dependencies]
|
||||
# Internal
|
||||
nxmesh-core.workspace = true
|
||||
nxmesh-proto.workspace = true
|
||||
migration = { path = "../../migration" }
|
||||
|
||||
# Core
|
||||
tokio.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
thiserror.workspace = true
|
||||
tracing.workspace = true
|
||||
tracing-subscriber.workspace = true
|
||||
|
||||
# Web
|
||||
axum = { workspace = true, features = ["ws"] }
|
||||
tower.workspace = true
|
||||
tower-http = { workspace = true, features = ["fs", "cors"] }
|
||||
|
||||
# OpenAPI
|
||||
utoipa = { version = "4", features = ["axum_extras"] }
|
||||
utoipa-swagger-ui = { version = "6", features = ["axum"] }
|
||||
|
||||
# gRPC
|
||||
tonic.workspace = true
|
||||
|
||||
# Database
|
||||
sea-orm.workspace = true
|
||||
sea-orm-migration = { workspace = true, features = ["runtime-tokio-native-tls", "sqlx-postgres"] }
|
||||
|
||||
# Async
|
||||
async-trait.workspace = true
|
||||
futures.workspace = true
|
||||
async-stream = "0.3"
|
||||
|
||||
# Config
|
||||
config.workspace = true
|
||||
toml.workspace = true
|
||||
|
||||
# Crypto
|
||||
argon2.workspace = true
|
||||
jsonwebtoken.workspace = true
|
||||
sha2.workspace = true
|
||||
hex.workspace = true
|
||||
|
||||
# Validation
|
||||
validator.workspace = true
|
||||
|
||||
# Time
|
||||
chrono.workspace = true
|
||||
|
||||
# UUID
|
||||
uuid.workspace = true
|
||||
|
||||
# Templating
|
||||
handlebars.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
tokio-test.workspace = true
|
||||
mockall.workspace = true
|
||||
122
crates/nxmesh-master/src/api/middleware/auth.rs
Normal file
122
crates/nxmesh-master/src/api/middleware/auth.rs
Normal file
@@ -0,0 +1,122 @@
|
||||
//! JWT Authentication middleware
|
||||
|
||||
use axum::{
|
||||
extract::{Request, State},
|
||||
http::StatusCode,
|
||||
middleware::Next,
|
||||
response::{IntoResponse, Response},
|
||||
Json,
|
||||
};
|
||||
use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, Validation};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::config::Settings;
|
||||
|
||||
/// Claims in the JWT token
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Claims {
|
||||
pub sub: String, // User ID
|
||||
pub email: String,
|
||||
pub org_id: Option<String>,
|
||||
pub role: String,
|
||||
pub exp: usize,
|
||||
pub iat: usize,
|
||||
}
|
||||
|
||||
/// Auth state
|
||||
#[derive(Clone)]
|
||||
pub struct AuthState {
|
||||
settings: Arc<Settings>,
|
||||
}
|
||||
|
||||
impl AuthState {
|
||||
pub fn new(settings: Arc<Settings>) -> Self {
|
||||
Self { settings }
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate a JWT token
|
||||
pub fn generate_token(
|
||||
user_id: &str,
|
||||
email: &str,
|
||||
org_id: Option<&str>,
|
||||
role: &str,
|
||||
settings: &Settings,
|
||||
) -> Result<String, jsonwebtoken::errors::Error> {
|
||||
let now = chrono::Utc::now().timestamp() as usize;
|
||||
let exp = now + (settings.auth.jwt_expiration_hours as usize * 3600);
|
||||
|
||||
let claims = Claims {
|
||||
sub: user_id.to_string(),
|
||||
email: email.to_string(),
|
||||
org_id: org_id.map(|s| s.to_string()),
|
||||
role: role.to_string(),
|
||||
exp,
|
||||
iat: now,
|
||||
};
|
||||
|
||||
encode(
|
||||
&Header::default(),
|
||||
&claims,
|
||||
&EncodingKey::from_secret(settings.auth.jwt_secret.as_bytes()),
|
||||
)
|
||||
}
|
||||
|
||||
/// Validate a JWT token
|
||||
pub fn validate_token(token: &str, settings: &Settings) -> Result<Claims, jsonwebtoken::errors::Error> {
|
||||
decode::<Claims>(
|
||||
token,
|
||||
&DecodingKey::from_secret(settings.auth.jwt_secret.as_bytes()),
|
||||
&Validation::default(),
|
||||
)
|
||||
.map(|data| data.claims)
|
||||
}
|
||||
|
||||
/// Authentication middleware
|
||||
pub async fn auth_middleware(
|
||||
State(state): State<AuthState>,
|
||||
mut request: Request,
|
||||
next: Next,
|
||||
) -> Response {
|
||||
// Extract token from Authorization header
|
||||
let auth_header = request
|
||||
.headers()
|
||||
.get("authorization")
|
||||
.and_then(|value| value.to_str().ok())
|
||||
.and_then(|value| value.strip_prefix("Bearer "));
|
||||
|
||||
let token = match auth_header {
|
||||
Some(token) => token,
|
||||
None => {
|
||||
return (
|
||||
StatusCode::UNAUTHORIZED,
|
||||
Json(serde_json::json!({
|
||||
"error": "Missing authorization token"
|
||||
})),
|
||||
)
|
||||
.into_response();
|
||||
}
|
||||
};
|
||||
|
||||
// Validate token
|
||||
match validate_token(token, &state.settings) {
|
||||
Ok(claims) => {
|
||||
// Add claims to request extensions
|
||||
request.extensions_mut().insert(claims);
|
||||
next.run(request).await
|
||||
}
|
||||
Err(_) => (
|
||||
StatusCode::UNAUTHORIZED,
|
||||
Json(serde_json::json!({
|
||||
"error": "Invalid token"
|
||||
})),
|
||||
)
|
||||
.into_response(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract claims from request extensions
|
||||
pub fn extract_claims(request: &Request) -> Option<&Claims> {
|
||||
request.extensions().get::<Claims>()
|
||||
}
|
||||
22
crates/nxmesh-master/src/api/middleware/mod.rs
Normal file
22
crates/nxmesh-master/src/api/middleware/mod.rs
Normal file
@@ -0,0 +1,22 @@
|
||||
//! API middleware
|
||||
|
||||
pub mod auth;
|
||||
|
||||
use axum::{
|
||||
extract::Request,
|
||||
middleware::Next,
|
||||
response::Response,
|
||||
};
|
||||
use tracing::info;
|
||||
|
||||
/// Logging middleware
|
||||
pub async fn log_request(req: Request, next: Next) -> Response {
|
||||
let method = req.method().clone();
|
||||
let uri = req.uri().clone();
|
||||
|
||||
let response = next.run(req).await;
|
||||
|
||||
info!("{} {} - {}", method, uri, response.status());
|
||||
|
||||
response
|
||||
}
|
||||
96
crates/nxmesh-master/src/api/mod.rs
Normal file
96
crates/nxmesh-master/src/api/mod.rs
Normal file
@@ -0,0 +1,96 @@
|
||||
//! REST API handlers
|
||||
|
||||
pub mod middleware;
|
||||
pub mod routes;
|
||||
pub mod v1;
|
||||
pub mod websocket;
|
||||
|
||||
use utoipa::OpenApi;
|
||||
|
||||
/// API Documentation
|
||||
#[derive(OpenApi)]
|
||||
#[openapi(
|
||||
paths(
|
||||
routes::health_check,
|
||||
routes::login,
|
||||
v1::organizations::list,
|
||||
v1::organizations::create,
|
||||
v1::organizations::get,
|
||||
v1::organizations::update,
|
||||
v1::organizations::delete,
|
||||
v1::workspaces::list,
|
||||
v1::workspaces::create,
|
||||
v1::workspaces::get,
|
||||
v1::workspaces::update,
|
||||
v1::workspaces::delete,
|
||||
v1::agents::list,
|
||||
v1::agents::get,
|
||||
v1::agents::create_token,
|
||||
v1::agents::delete,
|
||||
v1::agents::reload,
|
||||
v1::virtual_hosts::list,
|
||||
v1::virtual_hosts::create,
|
||||
v1::virtual_hosts::get,
|
||||
v1::virtual_hosts::update,
|
||||
v1::virtual_hosts::delete,
|
||||
v1::upstreams::list,
|
||||
v1::upstreams::create,
|
||||
v1::upstreams::get,
|
||||
v1::upstreams::update,
|
||||
v1::upstreams::delete,
|
||||
v1::certificates::list,
|
||||
v1::certificates::create,
|
||||
v1::certificates::get,
|
||||
v1::certificates::delete,
|
||||
),
|
||||
components(
|
||||
schemas(
|
||||
routes::LoginRequest,
|
||||
routes::LoginResponse,
|
||||
v1::organizations::OrganizationResponse,
|
||||
v1::organizations::CreateOrganizationRequest,
|
||||
v1::workspaces::WorkspaceResponse,
|
||||
v1::workspaces::CreateWorkspaceRequest,
|
||||
v1::agents::AgentResponse,
|
||||
v1::agents::CreateAgentTokenRequest,
|
||||
v1::agents::CreateAgentTokenResponse,
|
||||
v1::virtual_hosts::VirtualHostResponse,
|
||||
v1::virtual_hosts::CreateVirtualHostRequest,
|
||||
v1::upstreams::UpstreamResponse,
|
||||
v1::upstreams::CreateUpstreamRequest,
|
||||
v1::certificates::CertificateResponse,
|
||||
v1::certificates::CreateCertificateRequest,
|
||||
)
|
||||
),
|
||||
tags(
|
||||
(name = "Organizations", description = "Organization management"),
|
||||
(name = "Workspaces", description = "Workspace management"),
|
||||
(name = "Agents", description = "Agent management"),
|
||||
(name = "Virtual Hosts", description = "Virtual host configuration"),
|
||||
(name = "Upstreams", description = "Upstream configuration"),
|
||||
(name = "Certificates", description = "SSL certificate management"),
|
||||
)
|
||||
)]
|
||||
pub struct ApiDoc;
|
||||
|
||||
impl ApiDoc {
|
||||
/// Generate OpenAPI spec with Uuid schema added
|
||||
pub fn generate() -> utoipa::openapi::OpenApi {
|
||||
let mut openapi = Self::openapi();
|
||||
|
||||
// Add Uuid schema
|
||||
if let Some(components) = openapi.components.as_mut() {
|
||||
components.schemas.insert(
|
||||
"Uuid".to_string(),
|
||||
utoipa::openapi::schema::ObjectBuilder::new()
|
||||
.schema_type(utoipa::openapi::SchemaType::String)
|
||||
.format(Some(utoipa::openapi::SchemaFormat::Custom("uuid".to_string())))
|
||||
.description(Some("UUID string"))
|
||||
.example(Some(serde_json::json!("550e8400-e29b-41d4-a716-446655440000")))
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
|
||||
openapi
|
||||
}
|
||||
}
|
||||
114
crates/nxmesh-master/src/api/routes.rs
Normal file
114
crates/nxmesh-master/src/api/routes.rs
Normal file
@@ -0,0 +1,114 @@
|
||||
//! API route definitions
|
||||
|
||||
use axum::{
|
||||
routing::{get, post},
|
||||
Router,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::config::Settings;
|
||||
use crate::db::Database;
|
||||
use crate::api::middleware::auth::AuthState;
|
||||
use crate::api::ApiDoc;
|
||||
use crate::api::v1;
|
||||
|
||||
use super::middleware::auth::auth_middleware;
|
||||
use super::middleware::log_request;
|
||||
|
||||
use utoipa::OpenApi;
|
||||
use utoipa_swagger_ui::SwaggerUi;
|
||||
|
||||
/// Application state
|
||||
#[derive(Clone)]
|
||||
pub struct AppState {
|
||||
pub db: Database,
|
||||
pub settings: Arc<Settings>,
|
||||
}
|
||||
|
||||
/// Create the main API router
|
||||
pub fn create_router(state: AppState) -> Router {
|
||||
let auth_state = AuthState::new(state.settings.clone());
|
||||
|
||||
// Public routes (no auth required)
|
||||
let public_routes = Router::new()
|
||||
.route("/health", get(health_check))
|
||||
.route("/api/v1/auth/login", post(login))
|
||||
// Swagger UI (includes OpenAPI spec at /api/openapi.json)
|
||||
.merge(SwaggerUi::new("/swagger-ui").url("/api/openapi.json", ApiDoc::generate()));
|
||||
|
||||
// Protected routes (auth required)
|
||||
let protected_routes = Router::new()
|
||||
// Organizations
|
||||
.route("/api/v1/organizations", get(v1::organizations::list).post(v1::organizations::create))
|
||||
.route("/api/v1/organizations/:id", get(v1::organizations::get).patch(v1::organizations::update).delete(v1::organizations::delete))
|
||||
.route("/api/v1/organizations/:id/workspaces", get(v1::workspaces::list).post(v1::workspaces::create))
|
||||
|
||||
// Workspaces
|
||||
.route("/api/v1/workspaces/:id", get(v1::workspaces::get).patch(v1::workspaces::update).delete(v1::workspaces::delete))
|
||||
.route("/api/v1/workspaces/:id/agents", get(v1::agents::list))
|
||||
.route("/api/v1/workspaces/:id/virtual-hosts", get(v1::virtual_hosts::list).post(v1::virtual_hosts::create))
|
||||
.route("/api/v1/workspaces/:id/upstreams", get(v1::upstreams::list).post(v1::upstreams::create))
|
||||
.route("/api/v1/workspaces/:id/certificates", get(v1::certificates::list).post(v1::certificates::create))
|
||||
|
||||
// Agents
|
||||
.route("/api/v1/agents/:id", get(v1::agents::get).delete(v1::agents::delete))
|
||||
.route("/api/v1/agents/:id/reload", post(v1::agents::reload))
|
||||
.route("/api/v1/workspaces/:id/agents/tokens", post(v1::agents::create_token))
|
||||
|
||||
// Virtual Hosts
|
||||
.route("/api/v1/virtual-hosts/:id", get(v1::virtual_hosts::get).patch(v1::virtual_hosts::update).delete(v1::virtual_hosts::delete))
|
||||
|
||||
// Upstreams
|
||||
.route("/api/v1/upstreams/:id", get(v1::upstreams::get).patch(v1::upstreams::update).delete(v1::upstreams::delete))
|
||||
|
||||
// Certificates
|
||||
.route("/api/v1/certificates/:id", get(v1::certificates::get).delete(v1::certificates::delete))
|
||||
|
||||
// Middleware layer for protected routes
|
||||
.layer(axum::middleware::from_fn_with_state(auth_state.clone(), auth_middleware));
|
||||
|
||||
Router::new()
|
||||
.merge(public_routes)
|
||||
.merge(protected_routes)
|
||||
.layer(axum::middleware::from_fn(log_request))
|
||||
.with_state(state)
|
||||
}
|
||||
|
||||
/// Health check endpoint
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/health",
|
||||
responses(
|
||||
(status = 200, description = "Server is healthy", body = String),
|
||||
)
|
||||
)]
|
||||
async fn health_check() -> &'static str {
|
||||
"OK"
|
||||
}
|
||||
|
||||
/// Login endpoint
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/api/v1/auth/login",
|
||||
operation_id = "login",
|
||||
request_body = LoginRequest,
|
||||
responses(
|
||||
(status = 200, description = "Login successful", body = LoginResponse),
|
||||
)
|
||||
)]
|
||||
async fn login() -> axum::Json<serde_json::Value> {
|
||||
axum::Json(serde_json::json!({
|
||||
"token": "placeholder_token"
|
||||
}))
|
||||
}
|
||||
|
||||
#[derive(utoipa::ToSchema, serde::Deserialize)]
|
||||
pub struct LoginRequest {
|
||||
email: String,
|
||||
password: String,
|
||||
}
|
||||
|
||||
#[derive(utoipa::ToSchema, serde::Serialize)]
|
||||
pub struct LoginResponse {
|
||||
token: String,
|
||||
}
|
||||
133
crates/nxmesh-master/src/api/v1/agents.rs
Normal file
133
crates/nxmesh-master/src/api/v1/agents.rs
Normal file
@@ -0,0 +1,133 @@
|
||||
//! Agent API handlers
|
||||
|
||||
use axum::{
|
||||
extract::Path,
|
||||
Json,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use utoipa::ToSchema;
|
||||
use uuid::Uuid;
|
||||
|
||||
/// Agent response
|
||||
#[derive(Serialize, ToSchema)]
|
||||
pub struct AgentResponse {
|
||||
pub id: Uuid,
|
||||
pub workspace_id: Uuid,
|
||||
pub name: String,
|
||||
pub hostname: String,
|
||||
pub state: String,
|
||||
}
|
||||
|
||||
/// List agents
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/api/v1/workspaces/{id}/agents",
|
||||
operation_id = "listAgents",
|
||||
tag = "Agents",
|
||||
params(
|
||||
("id" = Uuid, Path, description = "Workspace ID")
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "List of agents", body = Vec<AgentResponse>),
|
||||
)
|
||||
)]
|
||||
pub async fn list(Path(ws_id): Path<Uuid>) -> Json<Vec<AgentResponse>> {
|
||||
let _ = ws_id;
|
||||
Json(vec![])
|
||||
}
|
||||
|
||||
/// Get agent by ID
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/api/v1/agents/{id}",
|
||||
operation_id = "getAgent",
|
||||
tag = "Agents",
|
||||
params(
|
||||
("id" = Uuid, Path, description = "Agent ID")
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "Agent found", body = AgentResponse),
|
||||
(status = 404, description = "Agent not found"),
|
||||
)
|
||||
)]
|
||||
pub async fn get(Path(id): Path<Uuid>) -> Json<AgentResponse> {
|
||||
Json(AgentResponse {
|
||||
id,
|
||||
workspace_id: Uuid::new_v4(),
|
||||
name: "agent-01".to_string(),
|
||||
hostname: "host-01".to_string(),
|
||||
state: "online".to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Create agent token request
|
||||
#[derive(Deserialize, ToSchema)]
|
||||
pub struct CreateAgentTokenRequest {
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
/// Create agent token response
|
||||
#[derive(Serialize, ToSchema)]
|
||||
pub struct CreateAgentTokenResponse {
|
||||
pub token: String,
|
||||
pub agent_id: Uuid,
|
||||
}
|
||||
|
||||
/// Create agent registration token
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/api/v1/workspaces/{id}/agents/tokens",
|
||||
operation_id = "createAgentToken",
|
||||
tag = "Agents",
|
||||
params(
|
||||
("id" = Uuid, Path, description = "Workspace ID")
|
||||
),
|
||||
request_body = CreateAgentTokenRequest,
|
||||
responses(
|
||||
(status = 201, description = "Token created", body = CreateAgentTokenResponse),
|
||||
)
|
||||
)]
|
||||
pub async fn create_token(
|
||||
Path(ws_id): Path<Uuid>,
|
||||
Json(req): Json<CreateAgentTokenRequest>,
|
||||
) -> Json<CreateAgentTokenResponse> {
|
||||
let _ = ws_id;
|
||||
Json(CreateAgentTokenResponse {
|
||||
token: "nxmesh_agent_token_placeholder".to_string(),
|
||||
agent_id: Uuid::new_v4(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Delete agent
|
||||
#[utoipa::path(
|
||||
delete,
|
||||
path = "/api/v1/agents/{id}",
|
||||
operation_id = "deleteAgent",
|
||||
tag = "Agents",
|
||||
params(
|
||||
("id" = Uuid, Path, description = "Agent ID")
|
||||
),
|
||||
responses(
|
||||
(status = 204, description = "Agent deleted"),
|
||||
)
|
||||
)]
|
||||
pub async fn delete(Path(_id): Path<Uuid>) -> &'static str {
|
||||
"{}"
|
||||
}
|
||||
|
||||
/// Reload agent nginx
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/api/v1/agents/{id}/reload",
|
||||
operation_id = "reloadAgent",
|
||||
tag = "Agents",
|
||||
params(
|
||||
("id" = Uuid, Path, description = "Agent ID")
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "Nginx reloaded successfully"),
|
||||
)
|
||||
)]
|
||||
pub async fn reload(Path(_id): Path<Uuid>) -> &'static str {
|
||||
r#"{"success": true}"#
|
||||
}
|
||||
106
crates/nxmesh-master/src/api/v1/certificates.rs
Normal file
106
crates/nxmesh-master/src/api/v1/certificates.rs
Normal file
@@ -0,0 +1,106 @@
|
||||
//! Certificate API handlers
|
||||
|
||||
use axum::{
|
||||
extract::Path,
|
||||
Json,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use utoipa::ToSchema;
|
||||
use uuid::Uuid;
|
||||
|
||||
/// Certificate response
|
||||
#[derive(Serialize, ToSchema)]
|
||||
pub struct CertificateResponse {
|
||||
pub id: Uuid,
|
||||
pub domain: String,
|
||||
pub status: String,
|
||||
}
|
||||
|
||||
/// List certificates
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/api/v1/workspaces/{id}/certificates",
|
||||
operation_id = "listCertificates",
|
||||
tag = "Certificates",
|
||||
params(
|
||||
("id" = Uuid, Path, description = "Workspace ID")
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "List of certificates", body = Vec<CertificateResponse>),
|
||||
)
|
||||
)]
|
||||
pub async fn list(Path(ws_id): Path<Uuid>) -> Json<Vec<CertificateResponse>> {
|
||||
let _ = ws_id;
|
||||
Json(vec![])
|
||||
}
|
||||
|
||||
/// Get certificate by ID
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/api/v1/certificates/{id}",
|
||||
operation_id = "getCertificate",
|
||||
tag = "Certificates",
|
||||
params(
|
||||
("id" = Uuid, Path, description = "Certificate ID")
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "Certificate found", body = CertificateResponse),
|
||||
(status = 404, description = "Certificate not found"),
|
||||
)
|
||||
)]
|
||||
pub async fn get(Path(id): Path<Uuid>) -> Json<CertificateResponse> {
|
||||
Json(CertificateResponse {
|
||||
id,
|
||||
domain: "example.com".to_string(),
|
||||
status: "active".to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Create certificate request
|
||||
#[derive(Deserialize, ToSchema)]
|
||||
pub struct CreateCertificateRequest {
|
||||
pub domain: String,
|
||||
}
|
||||
|
||||
/// Create certificate
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/api/v1/workspaces/{id}/certificates",
|
||||
operation_id = "createCertificate",
|
||||
tag = "Certificates",
|
||||
params(
|
||||
("id" = Uuid, Path, description = "Workspace ID")
|
||||
),
|
||||
request_body = CreateCertificateRequest,
|
||||
responses(
|
||||
(status = 201, description = "Certificate requested", body = CertificateResponse),
|
||||
)
|
||||
)]
|
||||
pub async fn create(
|
||||
Path(ws_id): Path<Uuid>,
|
||||
Json(req): Json<CreateCertificateRequest>,
|
||||
) -> Json<CertificateResponse> {
|
||||
let _ = ws_id;
|
||||
Json(CertificateResponse {
|
||||
id: Uuid::new_v4(),
|
||||
domain: req.domain,
|
||||
status: "pending".to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Delete certificate
|
||||
#[utoipa::path(
|
||||
delete,
|
||||
path = "/api/v1/certificates/{id}",
|
||||
operation_id = "deleteCertificate",
|
||||
tag = "Certificates",
|
||||
params(
|
||||
("id" = Uuid, Path, description = "Certificate ID")
|
||||
),
|
||||
responses(
|
||||
(status = 204, description = "Certificate deleted"),
|
||||
)
|
||||
)]
|
||||
pub async fn delete(Path(_id): Path<Uuid>) -> &'static str {
|
||||
"{}"
|
||||
}
|
||||
33
crates/nxmesh-master/src/api/v1/metrics.rs
Normal file
33
crates/nxmesh-master/src/api/v1/metrics.rs
Normal file
@@ -0,0 +1,33 @@
|
||||
//! Metrics API handlers
|
||||
|
||||
use axum::{
|
||||
extract::Path,
|
||||
Json,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
/// Metrics response
|
||||
#[derive(Serialize)]
|
||||
pub struct MetricsResponse {
|
||||
pub requests_total: u64,
|
||||
pub requests_per_second: f64,
|
||||
}
|
||||
|
||||
/// Get workspace metrics
|
||||
pub async fn get_workspace(Path(ws_id): Path<Uuid>) -> Json<MetricsResponse> {
|
||||
// TODO: Implement
|
||||
Json(MetricsResponse {
|
||||
requests_total: 0,
|
||||
requests_per_second: 0.0,
|
||||
})
|
||||
}
|
||||
|
||||
/// Get agent metrics
|
||||
pub async fn get_agent(Path(id): Path<Uuid>) -> Json<MetricsResponse> {
|
||||
// TODO: Implement
|
||||
Json(MetricsResponse {
|
||||
requests_total: 0,
|
||||
requests_per_second: 0.0,
|
||||
})
|
||||
}
|
||||
9
crates/nxmesh-master/src/api/v1/mod.rs
Normal file
9
crates/nxmesh-master/src/api/v1/mod.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
//! API v1 handlers
|
||||
|
||||
pub mod agents;
|
||||
pub mod certificates;
|
||||
pub mod metrics;
|
||||
pub mod organizations;
|
||||
pub mod upstreams;
|
||||
pub mod virtual_hosts;
|
||||
pub mod workspaces;
|
||||
129
crates/nxmesh-master/src/api/v1/organizations.rs
Normal file
129
crates/nxmesh-master/src/api/v1/organizations.rs
Normal file
@@ -0,0 +1,129 @@
|
||||
//! Organization API handlers
|
||||
|
||||
use axum::{
|
||||
extract::Path,
|
||||
Json,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use utoipa::ToSchema;
|
||||
use uuid::Uuid;
|
||||
|
||||
/// Organization response
|
||||
#[derive(Serialize, ToSchema)]
|
||||
pub struct OrganizationResponse {
|
||||
pub id: Uuid,
|
||||
pub name: String,
|
||||
pub slug: String,
|
||||
}
|
||||
|
||||
/// List organizations
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/api/v1/organizations",
|
||||
operation_id = "listOrganizations",
|
||||
tag = "Organizations",
|
||||
responses(
|
||||
(status = 200, description = "List of organizations", body = Vec<OrganizationResponse>),
|
||||
)
|
||||
)]
|
||||
pub async fn list() -> Json<Vec<OrganizationResponse>> {
|
||||
// TODO: Implement
|
||||
Json(vec![])
|
||||
}
|
||||
|
||||
/// Get organization by ID
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/api/v1/organizations/{id}",
|
||||
operation_id = "getOrganization",
|
||||
tag = "Organizations",
|
||||
params(
|
||||
("id" = Uuid, Path, description = "Organization ID")
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "Organization found", body = OrganizationResponse),
|
||||
(status = 404, description = "Organization not found"),
|
||||
)
|
||||
)]
|
||||
pub async fn get(Path(id): Path<Uuid>) -> Json<OrganizationResponse> {
|
||||
// TODO: Implement
|
||||
Json(OrganizationResponse {
|
||||
id,
|
||||
name: "Example".to_string(),
|
||||
slug: "example".to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Create organization request
|
||||
#[derive(Deserialize, ToSchema)]
|
||||
pub struct CreateOrganizationRequest {
|
||||
pub name: String,
|
||||
pub slug: String,
|
||||
}
|
||||
|
||||
/// Create organization
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/api/v1/organizations",
|
||||
operation_id = "createOrganization",
|
||||
tag = "Organizations",
|
||||
request_body = CreateOrganizationRequest,
|
||||
responses(
|
||||
(status = 201, description = "Organization created", body = OrganizationResponse),
|
||||
(status = 400, description = "Invalid request"),
|
||||
)
|
||||
)]
|
||||
pub async fn create(Json(req): Json<CreateOrganizationRequest>) -> Json<OrganizationResponse> {
|
||||
// TODO: Implement
|
||||
Json(OrganizationResponse {
|
||||
id: Uuid::new_v4(),
|
||||
name: req.name,
|
||||
slug: req.slug,
|
||||
})
|
||||
}
|
||||
|
||||
/// Update organization
|
||||
#[utoipa::path(
|
||||
patch,
|
||||
path = "/api/v1/organizations/{id}",
|
||||
operation_id = "updateOrganization",
|
||||
tag = "Organizations",
|
||||
params(
|
||||
("id" = Uuid, Path, description = "Organization ID")
|
||||
),
|
||||
request_body = CreateOrganizationRequest,
|
||||
responses(
|
||||
(status = 200, description = "Organization updated", body = OrganizationResponse),
|
||||
(status = 404, description = "Organization not found"),
|
||||
)
|
||||
)]
|
||||
pub async fn update(
|
||||
Path(id): Path<Uuid>,
|
||||
Json(req): Json<CreateOrganizationRequest>,
|
||||
) -> Json<OrganizationResponse> {
|
||||
// TODO: Implement
|
||||
Json(OrganizationResponse {
|
||||
id,
|
||||
name: req.name,
|
||||
slug: req.slug,
|
||||
})
|
||||
}
|
||||
|
||||
/// Delete organization
|
||||
#[utoipa::path(
|
||||
delete,
|
||||
path = "/api/v1/organizations/{id}",
|
||||
operation_id = "deleteOrganization",
|
||||
tag = "Organizations",
|
||||
params(
|
||||
("id" = Uuid, Path, description = "Organization ID")
|
||||
),
|
||||
responses(
|
||||
(status = 204, description = "Organization deleted"),
|
||||
(status = 404, description = "Organization not found"),
|
||||
)
|
||||
)]
|
||||
pub async fn delete(Path(_id): Path<Uuid>) -> &'static str {
|
||||
// TODO: Implement
|
||||
"{}"
|
||||
}
|
||||
132
crates/nxmesh-master/src/api/v1/upstreams.rs
Normal file
132
crates/nxmesh-master/src/api/v1/upstreams.rs
Normal file
@@ -0,0 +1,132 @@
|
||||
//! Upstream API handlers
|
||||
|
||||
use axum::{
|
||||
extract::Path,
|
||||
Json,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use utoipa::ToSchema;
|
||||
use uuid::Uuid;
|
||||
|
||||
/// Upstream response
|
||||
#[derive(Serialize, ToSchema)]
|
||||
pub struct UpstreamResponse {
|
||||
pub id: Uuid,
|
||||
pub name: String,
|
||||
pub algorithm: String,
|
||||
}
|
||||
|
||||
/// List upstreams
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/api/v1/workspaces/{id}/upstreams",
|
||||
operation_id = "listUpstreams",
|
||||
tag = "Upstreams",
|
||||
params(
|
||||
("id" = Uuid, Path, description = "Workspace ID")
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "List of upstreams", body = Vec<UpstreamResponse>),
|
||||
)
|
||||
)]
|
||||
pub async fn list(Path(ws_id): Path<Uuid>) -> Json<Vec<UpstreamResponse>> {
|
||||
let _ = ws_id;
|
||||
Json(vec![])
|
||||
}
|
||||
|
||||
/// Get upstream by ID
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/api/v1/upstreams/{id}",
|
||||
operation_id = "getUpstream",
|
||||
tag = "Upstreams",
|
||||
params(
|
||||
("id" = Uuid, Path, description = "Upstream ID")
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "Upstream found", body = UpstreamResponse),
|
||||
(status = 404, description = "Upstream not found"),
|
||||
)
|
||||
)]
|
||||
pub async fn get(Path(id): Path<Uuid>) -> Json<UpstreamResponse> {
|
||||
Json(UpstreamResponse {
|
||||
id,
|
||||
name: "backend-api".to_string(),
|
||||
algorithm: "round_robin".to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Create upstream request
|
||||
#[derive(Deserialize, ToSchema)]
|
||||
pub struct CreateUpstreamRequest {
|
||||
pub name: String,
|
||||
pub algorithm: String,
|
||||
}
|
||||
|
||||
/// Create upstream
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/api/v1/workspaces/{id}/upstreams",
|
||||
operation_id = "createUpstream",
|
||||
tag = "Upstreams",
|
||||
params(
|
||||
("id" = Uuid, Path, description = "Workspace ID")
|
||||
),
|
||||
request_body = CreateUpstreamRequest,
|
||||
responses(
|
||||
(status = 201, description = "Upstream created", body = UpstreamResponse),
|
||||
)
|
||||
)]
|
||||
pub async fn create(
|
||||
Path(ws_id): Path<Uuid>,
|
||||
Json(req): Json<CreateUpstreamRequest>,
|
||||
) -> Json<UpstreamResponse> {
|
||||
let _ = ws_id;
|
||||
Json(UpstreamResponse {
|
||||
id: Uuid::new_v4(),
|
||||
name: req.name,
|
||||
algorithm: req.algorithm,
|
||||
})
|
||||
}
|
||||
|
||||
/// Update upstream
|
||||
#[utoipa::path(
|
||||
patch,
|
||||
path = "/api/v1/upstreams/{id}",
|
||||
operation_id = "updateUpstream",
|
||||
tag = "Upstreams",
|
||||
params(
|
||||
("id" = Uuid, Path, description = "Upstream ID")
|
||||
),
|
||||
request_body = CreateUpstreamRequest,
|
||||
responses(
|
||||
(status = 200, description = "Upstream updated", body = UpstreamResponse),
|
||||
)
|
||||
)]
|
||||
pub async fn update(
|
||||
Path(id): Path<Uuid>,
|
||||
Json(req): Json<CreateUpstreamRequest>,
|
||||
) -> Json<UpstreamResponse> {
|
||||
Json(UpstreamResponse {
|
||||
id,
|
||||
name: req.name,
|
||||
algorithm: req.algorithm,
|
||||
})
|
||||
}
|
||||
|
||||
/// Delete upstream
|
||||
#[utoipa::path(
|
||||
delete,
|
||||
path = "/api/v1/upstreams/{id}",
|
||||
operation_id = "deleteUpstream",
|
||||
tag = "Upstreams",
|
||||
params(
|
||||
("id" = Uuid, Path, description = "Upstream ID")
|
||||
),
|
||||
responses(
|
||||
(status = 204, description = "Upstream deleted"),
|
||||
)
|
||||
)]
|
||||
pub async fn delete(Path(_id): Path<Uuid>) -> &'static str {
|
||||
"{}"
|
||||
}
|
||||
137
crates/nxmesh-master/src/api/v1/virtual_hosts.rs
Normal file
137
crates/nxmesh-master/src/api/v1/virtual_hosts.rs
Normal file
@@ -0,0 +1,137 @@
|
||||
//! Virtual Host API handlers
|
||||
|
||||
use axum::{
|
||||
extract::Path,
|
||||
Json,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use utoipa::ToSchema;
|
||||
use uuid::Uuid;
|
||||
|
||||
/// Virtual Host response
|
||||
#[derive(Serialize, ToSchema)]
|
||||
pub struct VirtualHostResponse {
|
||||
pub id: Uuid,
|
||||
pub name: String,
|
||||
pub server_name: String,
|
||||
pub listen_port: u16,
|
||||
}
|
||||
|
||||
/// List virtual hosts
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/api/v1/workspaces/{id}/virtual-hosts",
|
||||
operation_id = "listVirtualHosts",
|
||||
tag = "Virtual Hosts",
|
||||
params(
|
||||
("id" = Uuid, Path, description = "Workspace ID")
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "List of virtual hosts", body = Vec<VirtualHostResponse>),
|
||||
)
|
||||
)]
|
||||
pub async fn list(Path(ws_id): Path<Uuid>) -> Json<Vec<VirtualHostResponse>> {
|
||||
let _ = ws_id;
|
||||
Json(vec![])
|
||||
}
|
||||
|
||||
/// Get virtual host by ID
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/api/v1/virtual-hosts/{id}",
|
||||
operation_id = "getVirtualHost",
|
||||
tag = "Virtual Hosts",
|
||||
params(
|
||||
("id" = Uuid, Path, description = "Virtual Host ID")
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "Virtual host found", body = VirtualHostResponse),
|
||||
(status = 404, description = "Virtual host not found"),
|
||||
)
|
||||
)]
|
||||
pub async fn get(Path(id): Path<Uuid>) -> Json<VirtualHostResponse> {
|
||||
Json(VirtualHostResponse {
|
||||
id,
|
||||
name: "Main Website".to_string(),
|
||||
server_name: "example.com".to_string(),
|
||||
listen_port: 443,
|
||||
})
|
||||
}
|
||||
|
||||
/// Create virtual host request
|
||||
#[derive(Deserialize, ToSchema)]
|
||||
pub struct CreateVirtualHostRequest {
|
||||
pub name: String,
|
||||
pub server_name: String,
|
||||
pub listen_port: u16,
|
||||
}
|
||||
|
||||
/// Create virtual host
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/api/v1/workspaces/{id}/virtual-hosts",
|
||||
operation_id = "createVirtualHost",
|
||||
tag = "Virtual Hosts",
|
||||
params(
|
||||
("id" = Uuid, Path, description = "Workspace ID")
|
||||
),
|
||||
request_body = CreateVirtualHostRequest,
|
||||
responses(
|
||||
(status = 201, description = "Virtual host created", body = VirtualHostResponse),
|
||||
)
|
||||
)]
|
||||
pub async fn create(
|
||||
Path(ws_id): Path<Uuid>,
|
||||
Json(req): Json<CreateVirtualHostRequest>,
|
||||
) -> Json<VirtualHostResponse> {
|
||||
let _ = ws_id;
|
||||
Json(VirtualHostResponse {
|
||||
id: Uuid::new_v4(),
|
||||
name: req.name,
|
||||
server_name: req.server_name,
|
||||
listen_port: req.listen_port,
|
||||
})
|
||||
}
|
||||
|
||||
/// Update virtual host
|
||||
#[utoipa::path(
|
||||
patch,
|
||||
path = "/api/v1/virtual-hosts/{id}",
|
||||
operation_id = "updateVirtualHost",
|
||||
tag = "Virtual Hosts",
|
||||
params(
|
||||
("id" = Uuid, Path, description = "Virtual Host ID")
|
||||
),
|
||||
request_body = CreateVirtualHostRequest,
|
||||
responses(
|
||||
(status = 200, description = "Virtual host updated", body = VirtualHostResponse),
|
||||
)
|
||||
)]
|
||||
pub async fn update(
|
||||
Path(id): Path<Uuid>,
|
||||
Json(req): Json<CreateVirtualHostRequest>,
|
||||
) -> Json<VirtualHostResponse> {
|
||||
Json(VirtualHostResponse {
|
||||
id,
|
||||
name: req.name,
|
||||
server_name: req.server_name,
|
||||
listen_port: req.listen_port,
|
||||
})
|
||||
}
|
||||
|
||||
/// Delete virtual host
|
||||
#[utoipa::path(
|
||||
delete,
|
||||
path = "/api/v1/virtual-hosts/{id}",
|
||||
operation_id = "deleteVirtualHost",
|
||||
tag = "Virtual Hosts",
|
||||
params(
|
||||
("id" = Uuid, Path, description = "Virtual Host ID")
|
||||
),
|
||||
responses(
|
||||
(status = 204, description = "Virtual host deleted"),
|
||||
)
|
||||
)]
|
||||
pub async fn delete(Path(_id): Path<Uuid>) -> &'static str {
|
||||
"{}"
|
||||
}
|
||||
140
crates/nxmesh-master/src/api/v1/workspaces.rs
Normal file
140
crates/nxmesh-master/src/api/v1/workspaces.rs
Normal file
@@ -0,0 +1,140 @@
|
||||
//! Workspace API handlers
|
||||
|
||||
use axum::{
|
||||
extract::Path,
|
||||
Json,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use utoipa::ToSchema;
|
||||
use uuid::Uuid;
|
||||
|
||||
/// Workspace response
|
||||
#[derive(Serialize, ToSchema)]
|
||||
pub struct WorkspaceResponse {
|
||||
pub id: Uuid,
|
||||
pub organization_id: Uuid,
|
||||
pub name: String,
|
||||
pub slug: String,
|
||||
}
|
||||
|
||||
/// List workspaces
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/api/v1/organizations/{id}/workspaces",
|
||||
operation_id = "listWorkspaces",
|
||||
tag = "Workspaces",
|
||||
params(
|
||||
("id" = Uuid, Path, description = "Organization ID")
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "List of workspaces", body = Vec<WorkspaceResponse>),
|
||||
)
|
||||
)]
|
||||
pub async fn list(Path(org_id): Path<Uuid>) -> Json<Vec<WorkspaceResponse>> {
|
||||
// TODO: Implement using org_id
|
||||
let _ = org_id;
|
||||
Json(vec![])
|
||||
}
|
||||
|
||||
/// Get workspace by ID
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/api/v1/workspaces/{id}",
|
||||
operation_id = "getWorkspace",
|
||||
tag = "Workspaces",
|
||||
params(
|
||||
("id" = Uuid, Path, description = "Workspace ID")
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "Workspace found", body = WorkspaceResponse),
|
||||
(status = 404, description = "Workspace not found"),
|
||||
)
|
||||
)]
|
||||
pub async fn get(Path(id): Path<Uuid>) -> Json<WorkspaceResponse> {
|
||||
// TODO: Implement
|
||||
Json(WorkspaceResponse {
|
||||
id,
|
||||
organization_id: Uuid::new_v4(),
|
||||
name: "Production".to_string(),
|
||||
slug: "production".to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Create workspace request
|
||||
#[derive(Deserialize, ToSchema)]
|
||||
pub struct CreateWorkspaceRequest {
|
||||
pub name: String,
|
||||
pub slug: String,
|
||||
}
|
||||
|
||||
/// Create workspace
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/api/v1/organizations/{id}/workspaces",
|
||||
operation_id = "createWorkspace",
|
||||
tag = "Workspaces",
|
||||
params(
|
||||
("id" = Uuid, Path, description = "Organization ID")
|
||||
),
|
||||
request_body = CreateWorkspaceRequest,
|
||||
responses(
|
||||
(status = 201, description = "Workspace created", body = WorkspaceResponse),
|
||||
)
|
||||
)]
|
||||
pub async fn create(
|
||||
Path(org_id): Path<Uuid>,
|
||||
Json(req): Json<CreateWorkspaceRequest>,
|
||||
) -> Json<WorkspaceResponse> {
|
||||
// TODO: Implement
|
||||
Json(WorkspaceResponse {
|
||||
id: Uuid::new_v4(),
|
||||
organization_id: org_id,
|
||||
name: req.name,
|
||||
slug: req.slug,
|
||||
})
|
||||
}
|
||||
|
||||
/// Update workspace
|
||||
#[utoipa::path(
|
||||
patch,
|
||||
path = "/api/v1/workspaces/{id}",
|
||||
operation_id = "updateWorkspace",
|
||||
tag = "Workspaces",
|
||||
params(
|
||||
("id" = Uuid, Path, description = "Workspace ID")
|
||||
),
|
||||
request_body = CreateWorkspaceRequest,
|
||||
responses(
|
||||
(status = 200, description = "Workspace updated", body = WorkspaceResponse),
|
||||
)
|
||||
)]
|
||||
pub async fn update(
|
||||
Path(id): Path<Uuid>,
|
||||
Json(req): Json<CreateWorkspaceRequest>,
|
||||
) -> Json<WorkspaceResponse> {
|
||||
// TODO: Implement
|
||||
Json(WorkspaceResponse {
|
||||
id,
|
||||
organization_id: Uuid::new_v4(),
|
||||
name: req.name,
|
||||
slug: req.slug,
|
||||
})
|
||||
}
|
||||
|
||||
/// Delete workspace
|
||||
#[utoipa::path(
|
||||
delete,
|
||||
path = "/api/v1/workspaces/{id}",
|
||||
operation_id = "deleteWorkspace",
|
||||
tag = "Workspaces",
|
||||
params(
|
||||
("id" = Uuid, Path, description = "Workspace ID")
|
||||
),
|
||||
responses(
|
||||
(status = 204, description = "Workspace deleted"),
|
||||
)
|
||||
)]
|
||||
pub async fn delete(Path(_id): Path<Uuid>) -> &'static str {
|
||||
// TODO: Implement
|
||||
"{}"
|
||||
}
|
||||
22
crates/nxmesh-master/src/api/websocket.rs
Normal file
22
crates/nxmesh-master/src/api/websocket.rs
Normal file
@@ -0,0 +1,22 @@
|
||||
//! WebSocket handler for real-time events
|
||||
|
||||
use axum::{
|
||||
extract::ws::{WebSocket, WebSocketUpgrade},
|
||||
extract::State,
|
||||
response::Response,
|
||||
};
|
||||
|
||||
/// Handle WebSocket upgrade
|
||||
pub async fn ws_handler(
|
||||
ws: WebSocketUpgrade,
|
||||
State(state): State<()>,
|
||||
) -> Response {
|
||||
ws.on_upgrade(move |socket| handle_socket(socket, state))
|
||||
}
|
||||
|
||||
async fn handle_socket(mut socket: WebSocket, _state: ()) {
|
||||
// TODO: Implement WebSocket event handling
|
||||
while let Some(_msg) = socket.recv().await {
|
||||
// Handle messages
|
||||
}
|
||||
}
|
||||
9
crates/nxmesh-master/src/bin/gen-openapi.rs
Normal file
9
crates/nxmesh-master/src/bin/gen-openapi.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
//! Generate OpenAPI spec
|
||||
|
||||
use nxmesh_master::api::ApiDoc;
|
||||
|
||||
fn main() {
|
||||
let openapi = ApiDoc::generate();
|
||||
let spec = serde_json::to_string_pretty(&openapi).unwrap();
|
||||
println!("{}", spec);
|
||||
}
|
||||
5
crates/nxmesh-master/src/config/mod.rs
Normal file
5
crates/nxmesh-master/src/config/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
//! Configuration management
|
||||
|
||||
pub mod settings;
|
||||
|
||||
pub use settings::Settings;
|
||||
79
crates/nxmesh-master/src/config/settings.rs
Normal file
79
crates/nxmesh-master/src/config/settings.rs
Normal file
@@ -0,0 +1,79 @@
|
||||
//! Master configuration settings
|
||||
|
||||
use config::{Config, ConfigError, Environment, File};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Master server settings
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Settings {
|
||||
pub server: ServerSettings,
|
||||
pub database: DatabaseSettings,
|
||||
pub grpc: GrpcSettings,
|
||||
pub auth: AuthSettings,
|
||||
}
|
||||
|
||||
/// HTTP server settings
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ServerSettings {
|
||||
pub bind_address: String,
|
||||
pub port: u16,
|
||||
}
|
||||
|
||||
/// Database connection settings
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct DatabaseSettings {
|
||||
pub url: String,
|
||||
pub max_connections: u32,
|
||||
}
|
||||
|
||||
/// gRPC server settings
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct GrpcSettings {
|
||||
pub bind_address: String,
|
||||
pub port: u16,
|
||||
}
|
||||
|
||||
/// Authentication settings
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct AuthSettings {
|
||||
pub jwt_secret: String,
|
||||
pub jwt_expiration_hours: u64,
|
||||
}
|
||||
|
||||
impl Default for Settings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
server: ServerSettings {
|
||||
bind_address: "0.0.0.0".to_string(),
|
||||
port: 8080,
|
||||
},
|
||||
database: DatabaseSettings {
|
||||
url: "postgres://postgres:postgres@localhost/nxmesh".to_string(),
|
||||
max_connections: 10,
|
||||
},
|
||||
grpc: GrpcSettings {
|
||||
bind_address: "0.0.0.0".to_string(),
|
||||
port: 8443,
|
||||
},
|
||||
auth: AuthSettings {
|
||||
jwt_secret: "change-me-in-production".to_string(),
|
||||
jwt_expiration_hours: 24,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Settings {
|
||||
/// Load settings from config files and environment
|
||||
pub fn load() -> Result<Self, ConfigError> {
|
||||
let run_mode = std::env::var("RUN_MODE").unwrap_or_else(|_| "development".into());
|
||||
|
||||
let settings = Config::builder()
|
||||
.add_source(File::with_name("config/default").required(false))
|
||||
.add_source(File::with_name(&format!("config/{}", run_mode)).required(false))
|
||||
.add_source(Environment::with_prefix("NXMESH").separator("__"))
|
||||
.build()?;
|
||||
|
||||
settings.try_deserialize()
|
||||
}
|
||||
}
|
||||
30
crates/nxmesh-master/src/db/connection.rs
Normal file
30
crates/nxmesh-master/src/db/connection.rs
Normal file
@@ -0,0 +1,30 @@
|
||||
//! Database connection management
|
||||
|
||||
use sea_orm::{Database as SeaDatabase, DatabaseConnection, DbErr};
|
||||
|
||||
/// Database wrapper
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Database {
|
||||
conn: DatabaseConnection,
|
||||
}
|
||||
|
||||
impl Database {
|
||||
/// Create a new database connection
|
||||
pub async fn connect(database_url: &str) -> Result<Self, DbErr> {
|
||||
let conn = SeaDatabase::connect(database_url).await?;
|
||||
Ok(Self { conn })
|
||||
}
|
||||
|
||||
/// Get the database connection
|
||||
pub fn conn(&self) -> &DatabaseConnection {
|
||||
&self.conn
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for Database {
|
||||
type Target = DatabaseConnection;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.conn
|
||||
}
|
||||
}
|
||||
44
crates/nxmesh-master/src/db/entities/agents.rs
Normal file
44
crates/nxmesh-master/src/db/entities/agents.rs
Normal file
@@ -0,0 +1,44 @@
|
||||
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.19
|
||||
|
||||
use sea_orm::entity::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
|
||||
#[sea_orm(table_name = "agents")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key, auto_increment = false)]
|
||||
pub id: Uuid,
|
||||
pub workspace_id: Uuid,
|
||||
pub name: String,
|
||||
pub hostname: String,
|
||||
pub ip_address: Option<String>,
|
||||
pub version: Option<String>,
|
||||
pub state: String,
|
||||
pub deployment_mode: Option<String>,
|
||||
pub last_seen_at: Option<DateTimeWithTimeZone>,
|
||||
pub capabilities: Option<Json>,
|
||||
pub labels: Option<Json>,
|
||||
pub token_hash: Option<String>,
|
||||
pub created_at: DateTimeWithTimeZone,
|
||||
pub updated_at: DateTimeWithTimeZone,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {
|
||||
#[sea_orm(
|
||||
belongs_to = "super::workspaces::Entity",
|
||||
from = "Column::WorkspaceId",
|
||||
to = "super::workspaces::Column::Id",
|
||||
on_update = "NoAction",
|
||||
on_delete = "Cascade"
|
||||
)]
|
||||
Workspaces,
|
||||
}
|
||||
|
||||
impl Related<super::workspaces::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Workspaces.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
45
crates/nxmesh-master/src/db/entities/certificates.rs
Normal file
45
crates/nxmesh-master/src/db/entities/certificates.rs
Normal file
@@ -0,0 +1,45 @@
|
||||
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.19
|
||||
|
||||
use sea_orm::entity::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
|
||||
#[sea_orm(table_name = "certificates")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key, auto_increment = false)]
|
||||
pub id: Uuid,
|
||||
pub workspace_id: Uuid,
|
||||
pub domain: String,
|
||||
pub is_wildcard: bool,
|
||||
pub provider: Option<String>,
|
||||
pub status: Option<String>,
|
||||
pub issued_at: Option<DateTimeWithTimeZone>,
|
||||
pub expires_at: Option<DateTimeWithTimeZone>,
|
||||
pub auto_renew: bool,
|
||||
#[sea_orm(column_type = "Text", nullable)]
|
||||
pub certificate_pem: Option<String>,
|
||||
#[sea_orm(column_type = "Text", nullable)]
|
||||
pub private_key_pem: Option<String>,
|
||||
pub created_at: DateTimeWithTimeZone,
|
||||
pub updated_at: DateTimeWithTimeZone,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {
|
||||
#[sea_orm(
|
||||
belongs_to = "super::workspaces::Entity",
|
||||
from = "Column::WorkspaceId",
|
||||
to = "super::workspaces::Column::Id",
|
||||
on_update = "NoAction",
|
||||
on_delete = "Cascade"
|
||||
)]
|
||||
Workspaces,
|
||||
}
|
||||
|
||||
impl Related<super::workspaces::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Workspaces.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
11
crates/nxmesh-master/src/db/entities/mod.rs
Normal file
11
crates/nxmesh-master/src/db/entities/mod.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.19
|
||||
|
||||
pub mod prelude;
|
||||
|
||||
pub mod agents;
|
||||
pub mod certificates;
|
||||
pub mod organizations;
|
||||
pub mod upstreams;
|
||||
pub mod users;
|
||||
pub mod virtual_hosts;
|
||||
pub mod workspaces;
|
||||
39
crates/nxmesh-master/src/db/entities/organizations.rs
Normal file
39
crates/nxmesh-master/src/db/entities/organizations.rs
Normal file
@@ -0,0 +1,39 @@
|
||||
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.19
|
||||
|
||||
use sea_orm::entity::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
|
||||
#[sea_orm(table_name = "organizations")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key, auto_increment = false)]
|
||||
pub id: Uuid,
|
||||
pub name: String,
|
||||
#[sea_orm(unique)]
|
||||
pub slug: String,
|
||||
pub created_at: DateTimeWithTimeZone,
|
||||
pub updated_at: DateTimeWithTimeZone,
|
||||
pub settings: Option<Json>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {
|
||||
#[sea_orm(has_many = "super::users::Entity")]
|
||||
Users,
|
||||
#[sea_orm(has_many = "super::workspaces::Entity")]
|
||||
Workspaces,
|
||||
}
|
||||
|
||||
impl Related<super::users::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Users.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl Related<super::workspaces::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Workspaces.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
9
crates/nxmesh-master/src/db/entities/prelude.rs
Normal file
9
crates/nxmesh-master/src/db/entities/prelude.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.19
|
||||
|
||||
pub use super::agents::Entity as Agents;
|
||||
pub use super::certificates::Entity as Certificates;
|
||||
pub use super::organizations::Entity as Organizations;
|
||||
pub use super::upstreams::Entity as Upstreams;
|
||||
pub use super::users::Entity as Users;
|
||||
pub use super::virtual_hosts::Entity as VirtualHosts;
|
||||
pub use super::workspaces::Entity as Workspaces;
|
||||
40
crates/nxmesh-master/src/db/entities/upstreams.rs
Normal file
40
crates/nxmesh-master/src/db/entities/upstreams.rs
Normal file
@@ -0,0 +1,40 @@
|
||||
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.19
|
||||
|
||||
use sea_orm::entity::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
|
||||
#[sea_orm(table_name = "upstreams")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key, auto_increment = false)]
|
||||
pub id: Uuid,
|
||||
pub workspace_id: Uuid,
|
||||
pub name: String,
|
||||
pub algorithm: String,
|
||||
pub servers: Option<Json>,
|
||||
pub health_check: Option<Json>,
|
||||
pub keepalive_connections: Option<i32>,
|
||||
pub keepalive_timeout: Option<i32>,
|
||||
pub created_at: DateTimeWithTimeZone,
|
||||
pub updated_at: DateTimeWithTimeZone,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {
|
||||
#[sea_orm(
|
||||
belongs_to = "super::workspaces::Entity",
|
||||
from = "Column::WorkspaceId",
|
||||
to = "super::workspaces::Column::Id",
|
||||
on_update = "NoAction",
|
||||
on_delete = "Cascade"
|
||||
)]
|
||||
Workspaces,
|
||||
}
|
||||
|
||||
impl Related<super::workspaces::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Workspaces.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
39
crates/nxmesh-master/src/db/entities/users.rs
Normal file
39
crates/nxmesh-master/src/db/entities/users.rs
Normal file
@@ -0,0 +1,39 @@
|
||||
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.19
|
||||
|
||||
use sea_orm::entity::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
|
||||
#[sea_orm(table_name = "users")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key, auto_increment = false)]
|
||||
pub id: Uuid,
|
||||
#[sea_orm(unique)]
|
||||
pub email: String,
|
||||
pub password_hash: String,
|
||||
pub name: Option<String>,
|
||||
pub role: String,
|
||||
pub organization_id: Option<Uuid>,
|
||||
pub created_at: DateTimeWithTimeZone,
|
||||
pub updated_at: DateTimeWithTimeZone,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {
|
||||
#[sea_orm(
|
||||
belongs_to = "super::organizations::Entity",
|
||||
from = "Column::OrganizationId",
|
||||
to = "super::organizations::Column::Id",
|
||||
on_update = "NoAction",
|
||||
on_delete = "SetNull"
|
||||
)]
|
||||
Organizations,
|
||||
}
|
||||
|
||||
impl Related<super::organizations::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Organizations.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
44
crates/nxmesh-master/src/db/entities/virtual_hosts.rs
Normal file
44
crates/nxmesh-master/src/db/entities/virtual_hosts.rs
Normal file
@@ -0,0 +1,44 @@
|
||||
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.19
|
||||
|
||||
use sea_orm::entity::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
|
||||
#[sea_orm(table_name = "virtual_hosts")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key, auto_increment = false)]
|
||||
pub id: Uuid,
|
||||
pub workspace_id: Uuid,
|
||||
pub name: String,
|
||||
pub server_name: String,
|
||||
pub listen_port: i32,
|
||||
pub ssl_enabled: bool,
|
||||
pub ssl_certificate_id: Option<Uuid>,
|
||||
pub locations: Option<Json>,
|
||||
pub http2_enabled: bool,
|
||||
pub http3_enabled: bool,
|
||||
pub gzip_enabled: bool,
|
||||
pub target_agents: Option<Json>,
|
||||
pub created_at: DateTimeWithTimeZone,
|
||||
pub updated_at: DateTimeWithTimeZone,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {
|
||||
#[sea_orm(
|
||||
belongs_to = "super::workspaces::Entity",
|
||||
from = "Column::WorkspaceId",
|
||||
to = "super::workspaces::Column::Id",
|
||||
on_update = "NoAction",
|
||||
on_delete = "Cascade"
|
||||
)]
|
||||
Workspaces,
|
||||
}
|
||||
|
||||
impl Related<super::workspaces::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Workspaces.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
68
crates/nxmesh-master/src/db/entities/workspaces.rs
Normal file
68
crates/nxmesh-master/src/db/entities/workspaces.rs
Normal file
@@ -0,0 +1,68 @@
|
||||
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.19
|
||||
|
||||
use sea_orm::entity::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
|
||||
#[sea_orm(table_name = "workspaces")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key, auto_increment = false)]
|
||||
pub id: Uuid,
|
||||
pub organization_id: Uuid,
|
||||
pub name: String,
|
||||
pub slug: String,
|
||||
pub created_at: DateTimeWithTimeZone,
|
||||
pub updated_at: DateTimeWithTimeZone,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {
|
||||
#[sea_orm(has_many = "super::agents::Entity")]
|
||||
Agents,
|
||||
#[sea_orm(has_many = "super::certificates::Entity")]
|
||||
Certificates,
|
||||
#[sea_orm(
|
||||
belongs_to = "super::organizations::Entity",
|
||||
from = "Column::OrganizationId",
|
||||
to = "super::organizations::Column::Id",
|
||||
on_update = "NoAction",
|
||||
on_delete = "Cascade"
|
||||
)]
|
||||
Organizations,
|
||||
#[sea_orm(has_many = "super::upstreams::Entity")]
|
||||
Upstreams,
|
||||
#[sea_orm(has_many = "super::virtual_hosts::Entity")]
|
||||
VirtualHosts,
|
||||
}
|
||||
|
||||
impl Related<super::agents::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Agents.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl Related<super::certificates::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Certificates.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl Related<super::organizations::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Organizations.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl Related<super::upstreams::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Upstreams.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl Related<super::virtual_hosts::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::VirtualHosts.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
8
crates/nxmesh-master/src/db/mod.rs
Normal file
8
crates/nxmesh-master/src/db/mod.rs
Normal file
@@ -0,0 +1,8 @@
|
||||
//! Database layer
|
||||
|
||||
pub mod connection;
|
||||
pub mod entities;
|
||||
pub mod repositories;
|
||||
|
||||
pub use connection::Database;
|
||||
pub use migration::Migrator;
|
||||
19
crates/nxmesh-master/src/db/repositories/agent_repository.rs
Normal file
19
crates/nxmesh-master/src/db/repositories/agent_repository.rs
Normal file
@@ -0,0 +1,19 @@
|
||||
//! Agent repository
|
||||
|
||||
use uuid::Uuid;
|
||||
|
||||
/// Agent repository
|
||||
pub struct AgentRepository;
|
||||
|
||||
impl AgentRepository {
|
||||
/// Create a new repository instance
|
||||
pub fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
/// Find agent by ID
|
||||
pub async fn find_by_id(&self, _id: Uuid) -> Option<()> {
|
||||
// TODO: Implement
|
||||
None
|
||||
}
|
||||
}
|
||||
5
crates/nxmesh-master/src/db/repositories/mod.rs
Normal file
5
crates/nxmesh-master/src/db/repositories/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
//! Repository implementations
|
||||
|
||||
pub mod agent_repository;
|
||||
pub mod organization_repository;
|
||||
pub mod workspace_repository;
|
||||
@@ -0,0 +1,19 @@
|
||||
//! Organization repository
|
||||
|
||||
use uuid::Uuid;
|
||||
|
||||
/// Organization repository
|
||||
pub struct OrganizationRepository;
|
||||
|
||||
impl OrganizationRepository {
|
||||
/// Create a new repository instance
|
||||
pub fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
/// Find organization by ID
|
||||
pub async fn find_by_id(&self, _id: Uuid) -> Option<()> {
|
||||
// TODO: Implement
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
//! Workspace repository
|
||||
|
||||
use uuid::Uuid;
|
||||
|
||||
/// Workspace repository
|
||||
pub struct WorkspaceRepository;
|
||||
|
||||
impl WorkspaceRepository {
|
||||
/// Create a new repository instance
|
||||
pub fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
/// Find workspace by ID
|
||||
pub async fn find_by_id(&self, _id: Uuid) -> Option<()> {
|
||||
// TODO: Implement
|
||||
None
|
||||
}
|
||||
}
|
||||
28
crates/nxmesh-master/src/domain/agent.rs
Normal file
28
crates/nxmesh-master/src/domain/agent.rs
Normal file
@@ -0,0 +1,28 @@
|
||||
//! Agent domain entity
|
||||
|
||||
use uuid::Uuid;
|
||||
|
||||
/// Agent domain entity
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Agent {
|
||||
pub id: Uuid,
|
||||
pub workspace_id: Uuid,
|
||||
pub name: String,
|
||||
pub hostname: String,
|
||||
}
|
||||
|
||||
impl Agent {
|
||||
/// Create a new agent entity
|
||||
pub fn new(
|
||||
workspace_id: Uuid,
|
||||
name: impl Into<String>,
|
||||
hostname: impl Into<String>,
|
||||
) -> Self {
|
||||
Self {
|
||||
id: Uuid::new_v4(),
|
||||
workspace_id,
|
||||
name: name.into(),
|
||||
hostname: hostname.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
11
crates/nxmesh-master/src/domain/config.rs
Normal file
11
crates/nxmesh-master/src/domain/config.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
//! Config domain entity
|
||||
|
||||
use uuid::Uuid;
|
||||
|
||||
/// Config domain entity
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Config {
|
||||
pub id: Uuid,
|
||||
pub workspace_id: Uuid,
|
||||
pub name: String,
|
||||
}
|
||||
5
crates/nxmesh-master/src/domain/mod.rs
Normal file
5
crates/nxmesh-master/src/domain/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
//! Domain entities
|
||||
|
||||
pub mod agent;
|
||||
pub mod config;
|
||||
pub mod organization;
|
||||
22
crates/nxmesh-master/src/domain/organization.rs
Normal file
22
crates/nxmesh-master/src/domain/organization.rs
Normal file
@@ -0,0 +1,22 @@
|
||||
//! Organization domain entity
|
||||
|
||||
use uuid::Uuid;
|
||||
|
||||
/// Organization domain entity
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Organization {
|
||||
pub id: Uuid,
|
||||
pub name: String,
|
||||
pub slug: String,
|
||||
}
|
||||
|
||||
impl Organization {
|
||||
/// Create a new organization
|
||||
pub fn new(name: impl Into<String>, slug: impl Into<String>) -> Self {
|
||||
Self {
|
||||
id: Uuid::new_v4(),
|
||||
name: name.into(),
|
||||
slug: slug.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
40
crates/nxmesh-master/src/events/bus.rs
Normal file
40
crates/nxmesh-master/src/events/bus.rs
Normal file
@@ -0,0 +1,40 @@
|
||||
//! Event bus implementation
|
||||
|
||||
use tokio::sync::broadcast;
|
||||
|
||||
/// Event bus for internal communication
|
||||
pub struct EventBus {
|
||||
sender: broadcast::Sender<Event>,
|
||||
}
|
||||
|
||||
/// Events that can be broadcast
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Event {
|
||||
AgentConnected { agent_id: String },
|
||||
AgentDisconnected { agent_id: String },
|
||||
ConfigUpdated { config_id: String },
|
||||
}
|
||||
|
||||
impl EventBus {
|
||||
/// Create a new event bus
|
||||
pub fn new() -> Self {
|
||||
let (sender, _receiver) = broadcast::channel(100);
|
||||
Self { sender }
|
||||
}
|
||||
|
||||
/// Subscribe to events
|
||||
pub fn subscribe(&self) -> broadcast::Receiver<Event> {
|
||||
self.sender.subscribe()
|
||||
}
|
||||
|
||||
/// Publish an event
|
||||
pub fn publish(&self, event: Event) {
|
||||
let _ = self.sender.send(event);
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for EventBus {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
19
crates/nxmesh-master/src/events/handlers.rs
Normal file
19
crates/nxmesh-master/src/events/handlers.rs
Normal file
@@ -0,0 +1,19 @@
|
||||
//! Event handlers
|
||||
|
||||
use super::bus::Event;
|
||||
use tracing::info;
|
||||
|
||||
/// Handle events
|
||||
pub async fn handle_event(event: Event) {
|
||||
match event {
|
||||
Event::AgentConnected { agent_id } => {
|
||||
info!("Agent connected: {}", agent_id);
|
||||
}
|
||||
Event::AgentDisconnected { agent_id } => {
|
||||
info!("Agent disconnected: {}", agent_id);
|
||||
}
|
||||
Event::ConfigUpdated { config_id } => {
|
||||
info!("Config updated: {}", config_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
4
crates/nxmesh-master/src/events/mod.rs
Normal file
4
crates/nxmesh-master/src/events/mod.rs
Normal file
@@ -0,0 +1,4 @@
|
||||
//! Event bus
|
||||
|
||||
pub mod bus;
|
||||
pub mod handlers;
|
||||
194
crates/nxmesh-master/src/grpc/agent_service.rs
Normal file
194
crates/nxmesh-master/src/grpc/agent_service.rs
Normal file
@@ -0,0 +1,194 @@
|
||||
//! Agent gRPC service
|
||||
|
||||
use chrono::Utc;
|
||||
use nxmesh_proto::{
|
||||
agent_service_server::AgentService, agent::AgentMessage, Ack, ConfigUpdate, HealthReport,
|
||||
MasterMessage, MetricsBatch, RegistrationRequest, RegistrationResponse,
|
||||
};
|
||||
use sea_orm::ActiveModelTrait;
|
||||
use sea_orm::Set;
|
||||
use tonic::{Request, Response, Status, Streaming};
|
||||
use futures::Stream;
|
||||
use std::pin::Pin;
|
||||
use tracing::{error, info, warn};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::db::entities::agent;
|
||||
use crate::db::Database;
|
||||
|
||||
/// Agent service implementation
|
||||
#[derive(Debug)]
|
||||
pub struct AgentServiceImpl {
|
||||
db: Database,
|
||||
}
|
||||
|
||||
impl AgentServiceImpl {
|
||||
pub fn new(db: Database) -> Self {
|
||||
Self { db }
|
||||
}
|
||||
|
||||
async fn handle_registration(
|
||||
&self,
|
||||
request: RegistrationRequest,
|
||||
) -> Result<RegistrationResponse, Status> {
|
||||
info!("Agent registration request: hostname={}", request.hostname);
|
||||
|
||||
// TODO: Validate token properly
|
||||
// For now, create a new agent record
|
||||
let agent_id = Uuid::new_v4();
|
||||
|
||||
let now = Utc::now();
|
||||
let agent = agent::ActiveModel {
|
||||
id: Set(agent_id),
|
||||
workspace_id: Set(Uuid::nil()), // TODO: Get from token
|
||||
name: Set(request.hostname.clone()),
|
||||
hostname: Set(request.hostname),
|
||||
ip_address: Set(Some(request.ip_address)),
|
||||
version: Set(Some(request.version)),
|
||||
state: Set("online".to_string()),
|
||||
deployment_mode: Set(Some(format!("{:?}", request.deployment_mode))),
|
||||
last_seen_at: Set(Some(now.into())),
|
||||
capabilities: Set(Some(serde_json::json!(request.capabilities))),
|
||||
labels: Set(Some(serde_json::json!(request.labels))),
|
||||
token_hash: Set(None),
|
||||
created_at: Set(now.into()),
|
||||
updated_at: Set(now.into()),
|
||||
};
|
||||
|
||||
match agent.insert(self.db.conn()).await {
|
||||
Ok(_) => {
|
||||
info!("Agent registered successfully: {}", agent_id);
|
||||
Ok(RegistrationResponse {
|
||||
agent_id: agent_id.to_string(),
|
||||
success: true,
|
||||
error_message: String::new(),
|
||||
heartbeat_interval_seconds: 30,
|
||||
})
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to register agent: {}", e);
|
||||
Err(Status::internal(format!("Database error: {}", e)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_health_report(&self, report: HealthReport, agent_id: &str) -> Result<(), Status> {
|
||||
let agent_uuid = Uuid::parse_str(agent_id)
|
||||
.map_err(|_| Status::invalid_argument("Invalid agent ID"))?;
|
||||
|
||||
// Update agent's last_seen_at
|
||||
let now = Utc::now();
|
||||
let agent = agent::ActiveModel {
|
||||
id: Set(agent_uuid),
|
||||
last_seen_at: Set(Some(now.into())),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
if let Err(e) = agent.update(self.db.conn()).await {
|
||||
warn!("Failed to update agent last_seen: {}", e);
|
||||
}
|
||||
|
||||
// TODO: Store health report in time-series database
|
||||
if let Some(nginx) = report.nginx {
|
||||
info!(
|
||||
"Health report from {}: nginx_running={}",
|
||||
agent_id, nginx.is_running
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[tonic::async_trait]
|
||||
impl AgentService for AgentServiceImpl {
|
||||
type StreamStream = Pin<Box<dyn Stream<Item = Result<MasterMessage, Status>> + Send>>;
|
||||
|
||||
/// Bidirectional streaming RPC
|
||||
async fn stream(
|
||||
&self,
|
||||
request: Request<Streaming<AgentMessage>>,
|
||||
) -> Result<Response<Self::StreamStream>, Status> {
|
||||
let mut stream = request.into_inner();
|
||||
let db = self.db.clone();
|
||||
|
||||
let output_stream = async_stream::try_stream! {
|
||||
while let Some(result) = stream.message().await? {
|
||||
let msg = result;
|
||||
|
||||
// Handle different message types via payload
|
||||
if let Some(payload) = msg.payload {
|
||||
use nxmesh_proto::agent_message::Payload;
|
||||
match payload {
|
||||
Payload::Registration(_reg) => {
|
||||
info!("Received registration in stream from agent {}", msg.agent_id);
|
||||
}
|
||||
Payload::Health(health) => {
|
||||
if let Err(e) = Self::handle_health_report(&Self { db: db.clone() }, health, &msg.agent_id).await {
|
||||
warn!("Failed to handle health report: {}", e);
|
||||
}
|
||||
}
|
||||
Payload::Metrics(_metrics) => {
|
||||
info!("Received metrics from agent {}", msg.agent_id);
|
||||
}
|
||||
Payload::ConfigStatus(_status) => {
|
||||
info!("Received config status from agent {}", msg.agent_id);
|
||||
}
|
||||
Payload::Logs(_logs) => {
|
||||
info!("Received logs from agent {}", msg.agent_id);
|
||||
}
|
||||
Payload::Event(_event) => {
|
||||
info!("Received event from agent {}", msg.agent_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Echo back a response
|
||||
yield MasterMessage {
|
||||
timestamp: Utc::now().timestamp(),
|
||||
..Default::default()
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Response::new(Box::pin(output_stream)))
|
||||
}
|
||||
|
||||
/// Report health status
|
||||
async fn report_health(
|
||||
&self,
|
||||
request: Request<HealthReport>,
|
||||
) -> Result<Response<Ack>, Status> {
|
||||
// Extract agent ID from metadata before consuming request
|
||||
let agent_id = request.metadata()
|
||||
.get("x-agent-id")
|
||||
.and_then(|v| v.to_str().ok())
|
||||
.unwrap_or("unknown")
|
||||
.to_string();
|
||||
|
||||
let report = request.into_inner();
|
||||
|
||||
self.handle_health_report(report, &agent_id).await?;
|
||||
|
||||
Ok(Response::new(Ack {
|
||||
message_id: "health".to_string(),
|
||||
success: true,
|
||||
error_message: String::new(),
|
||||
}))
|
||||
}
|
||||
|
||||
/// Report metrics
|
||||
async fn report_metrics(
|
||||
&self,
|
||||
request: Request<MetricsBatch>,
|
||||
) -> Result<Response<Ack>, Status> {
|
||||
let metrics = request.into_inner();
|
||||
info!("Metrics batch received with {} metrics", metrics.metrics.len());
|
||||
|
||||
Ok(Response::new(Ack {
|
||||
message_id: "metrics".to_string(),
|
||||
success: true,
|
||||
error_message: String::new(),
|
||||
}))
|
||||
}
|
||||
}
|
||||
9
crates/nxmesh-master/src/grpc/interceptor.rs
Normal file
9
crates/nxmesh-master/src/grpc/interceptor.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
//! gRPC interceptors
|
||||
|
||||
use tonic::{Request, Status};
|
||||
|
||||
/// Authentication interceptor
|
||||
pub fn auth_interceptor(req: Request<()>) -> Result<Request<()>, Status> {
|
||||
// TODO: Implement authentication
|
||||
Ok(req)
|
||||
}
|
||||
5
crates/nxmesh-master/src/grpc/mod.rs
Normal file
5
crates/nxmesh-master/src/grpc/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
//! gRPC service
|
||||
|
||||
pub mod agent_service;
|
||||
pub mod interceptor;
|
||||
pub mod server;
|
||||
23
crates/nxmesh-master/src/grpc/server.rs
Normal file
23
crates/nxmesh-master/src/grpc/server.rs
Normal file
@@ -0,0 +1,23 @@
|
||||
//! gRPC server
|
||||
|
||||
use tonic::transport::Server;
|
||||
|
||||
use crate::db::Database;
|
||||
|
||||
use super::agent_service::AgentServiceImpl;
|
||||
|
||||
/// Start the gRPC server
|
||||
pub async fn start(bind_address: &str, db: Database) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let addr = bind_address.parse()?;
|
||||
|
||||
let agent_service = AgentServiceImpl::new(db);
|
||||
|
||||
Server::builder()
|
||||
.add_service(nxmesh_proto::agent_service_server::AgentServiceServer::new(
|
||||
agent_service,
|
||||
))
|
||||
.serve(addr)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
11
crates/nxmesh-master/src/infrastructure/acme/mod.rs
Normal file
11
crates/nxmesh-master/src/infrastructure/acme/mod.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
//! ACME/Let's Encrypt integration
|
||||
|
||||
/// ACME client for certificate management
|
||||
pub struct AcmeClient;
|
||||
|
||||
impl AcmeClient {
|
||||
/// Create a new ACME client
|
||||
pub fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
5
crates/nxmesh-master/src/infrastructure/mod.rs
Normal file
5
crates/nxmesh-master/src/infrastructure/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
//! External integrations
|
||||
|
||||
pub mod acme;
|
||||
pub mod notifier;
|
||||
pub mod storage;
|
||||
11
crates/nxmesh-master/src/infrastructure/notifier/mod.rs
Normal file
11
crates/nxmesh-master/src/infrastructure/notifier/mod.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
//! Notification system
|
||||
|
||||
/// Notifier for sending alerts
|
||||
pub struct Notifier;
|
||||
|
||||
impl Notifier {
|
||||
/// Create a new notifier
|
||||
pub fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
11
crates/nxmesh-master/src/infrastructure/storage/mod.rs
Normal file
11
crates/nxmesh-master/src/infrastructure/storage/mod.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
//! Object storage integration
|
||||
|
||||
/// Storage client
|
||||
pub struct StorageClient;
|
||||
|
||||
impl StorageClient {
|
||||
/// Create a new storage client
|
||||
pub fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
85
crates/nxmesh-master/src/lib.rs
Normal file
85
crates/nxmesh-master/src/lib.rs
Normal file
@@ -0,0 +1,85 @@
|
||||
//! NxMesh Master Library
|
||||
//!
|
||||
//! This crate implements the control plane for NxMesh.
|
||||
|
||||
pub mod api;
|
||||
pub mod config;
|
||||
pub mod db;
|
||||
pub mod domain;
|
||||
pub mod events;
|
||||
pub mod grpc;
|
||||
pub mod infrastructure;
|
||||
pub mod services;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use config::Settings;
|
||||
use db::{Database, Migrator};
|
||||
use sea_orm_migration::MigratorTrait;
|
||||
use tokio::net::TcpListener;
|
||||
use tracing::{error, info};
|
||||
|
||||
/// Start the master server
|
||||
pub async fn start(settings: Settings) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let settings = Arc::new(settings);
|
||||
|
||||
info!("Connecting to database...");
|
||||
let db = Database::connect(&settings.database.url).await.map_err(|e| {
|
||||
error!("Failed to connect to database: {}", e);
|
||||
e
|
||||
})?;
|
||||
|
||||
info!("Running database migrations...");
|
||||
Migrator::up(db.conn(), None).await.map_err(|e| {
|
||||
error!("Failed to run migrations: {}", e);
|
||||
e
|
||||
})?;
|
||||
info!("Database migrations complete");
|
||||
|
||||
// Create application state
|
||||
let app_state = api::routes::AppState {
|
||||
db: db.clone(),
|
||||
settings: settings.clone(),
|
||||
};
|
||||
|
||||
// Create router
|
||||
let app = api::routes::create_router(app_state);
|
||||
|
||||
// Start HTTP server
|
||||
let http_addr = format!("{}:{}", settings.server.bind_address, settings.server.port);
|
||||
info!("Starting HTTP server on {}", http_addr);
|
||||
|
||||
let http_listener = TcpListener::bind(&http_addr).await?;
|
||||
|
||||
// Start gRPC server in a separate task
|
||||
let grpc_settings = settings.clone();
|
||||
let grpc_db = db.clone();
|
||||
let grpc_handle = tokio::spawn(async move {
|
||||
let grpc_addr = format!("{}:{}", grpc_settings.grpc.bind_address, grpc_settings.grpc.port);
|
||||
info!("Starting gRPC server on {}", grpc_addr);
|
||||
|
||||
if let Err(e) = grpc::server::start(&grpc_addr, grpc_db).await {
|
||||
error!("gRPC server error: {}", e);
|
||||
}
|
||||
});
|
||||
|
||||
// Run HTTP server
|
||||
info!("Master server ready!");
|
||||
|
||||
tokio::select! {
|
||||
result = axum::serve(http_listener, app) => {
|
||||
if let Err(e) = result {
|
||||
error!("HTTP server error: {}", e);
|
||||
}
|
||||
}
|
||||
_ = tokio::signal::ctrl_c() => {
|
||||
info!("Shutdown signal received");
|
||||
}
|
||||
}
|
||||
|
||||
// Cancel gRPC server
|
||||
grpc_handle.abort();
|
||||
|
||||
info!("Master server shutdown complete");
|
||||
Ok(())
|
||||
}
|
||||
35
crates/nxmesh-master/src/main.rs
Normal file
35
crates/nxmesh-master/src/main.rs
Normal file
@@ -0,0 +1,35 @@
|
||||
//! NxMesh Master - Control Plane
|
||||
//!
|
||||
//! The master is the central control plane that manages agents and distributes
|
||||
//! nginx configurations across the cluster.
|
||||
|
||||
use tracing::{info, error};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Initialize tracing
|
||||
tracing_subscriber::fmt()
|
||||
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
|
||||
.init();
|
||||
|
||||
info!("Starting NxMesh Master v{}", env!("CARGO_PKG_VERSION"));
|
||||
|
||||
// Load configuration
|
||||
let config = match nxmesh_master::config::Settings::load() {
|
||||
Ok(cfg) => cfg,
|
||||
Err(e) => {
|
||||
error!("Failed to load configuration: {}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
info!("Configuration loaded successfully");
|
||||
|
||||
// Start the master
|
||||
if let Err(e) = nxmesh_master::start(config).await {
|
||||
error!("Master error: {}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
38
crates/nxmesh-master/src/services/agent_service.rs
Normal file
38
crates/nxmesh-master/src/services/agent_service.rs
Normal file
@@ -0,0 +1,38 @@
|
||||
//! Agent service
|
||||
|
||||
use uuid::Uuid;
|
||||
|
||||
/// Agent service
|
||||
pub struct AgentService;
|
||||
|
||||
impl AgentService {
|
||||
/// Create a new service instance
|
||||
pub fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
/// Register agent
|
||||
pub async fn register(
|
||||
&self,
|
||||
workspace_id: Uuid,
|
||||
name: &str,
|
||||
hostname: &str,
|
||||
) -> Result<Uuid, String> {
|
||||
// TODO: Implement
|
||||
let id = Uuid::new_v4();
|
||||
tracing::info!(
|
||||
"Registering agent: {} ({}) in workspace {} -> {}",
|
||||
name,
|
||||
hostname,
|
||||
workspace_id,
|
||||
id
|
||||
);
|
||||
Ok(id)
|
||||
}
|
||||
|
||||
/// Generate registration token
|
||||
pub async fn generate_token(&self, agent_id: Uuid) -> Result<String, String> {
|
||||
// TODO: Implement proper token generation
|
||||
Ok(format!("nxmesh_agent_token_{}", agent_id))
|
||||
}
|
||||
}
|
||||
25
crates/nxmesh-master/src/services/auth_service.rs
Normal file
25
crates/nxmesh-master/src/services/auth_service.rs
Normal file
@@ -0,0 +1,25 @@
|
||||
//! Authentication service
|
||||
|
||||
/// Auth service
|
||||
pub struct AuthService;
|
||||
|
||||
impl AuthService {
|
||||
/// Create a new service instance
|
||||
pub fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
/// Authenticate user
|
||||
pub async fn authenticate(&self, email: &str, password: &str) -> Result<String, String> {
|
||||
// TODO: Implement
|
||||
tracing::info!("Authenticating user: {}", email);
|
||||
Ok("jwt_token_placeholder".to_string())
|
||||
}
|
||||
|
||||
/// Validate token
|
||||
pub async fn validate_token(&self, token: &str) -> Result<bool, String> {
|
||||
// TODO: Implement
|
||||
tracing::info!("Validating token: {}", token);
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
26
crates/nxmesh-master/src/services/certificate_service.rs
Normal file
26
crates/nxmesh-master/src/services/certificate_service.rs
Normal file
@@ -0,0 +1,26 @@
|
||||
//! Certificate service
|
||||
|
||||
use uuid::Uuid;
|
||||
|
||||
/// Certificate service
|
||||
pub struct CertificateService;
|
||||
|
||||
impl CertificateService {
|
||||
/// Create a new service instance
|
||||
pub fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
/// Request certificate
|
||||
pub async fn request(&self, workspace_id: Uuid, domain: &str) -> Result<Uuid, String> {
|
||||
// TODO: Implement
|
||||
let id = Uuid::new_v4();
|
||||
tracing::info!(
|
||||
"Requesting certificate for {} in workspace {} -> {}",
|
||||
domain,
|
||||
workspace_id,
|
||||
id
|
||||
);
|
||||
Ok(id)
|
||||
}
|
||||
}
|
||||
20
crates/nxmesh-master/src/services/config_service.rs
Normal file
20
crates/nxmesh-master/src/services/config_service.rs
Normal file
@@ -0,0 +1,20 @@
|
||||
//! Config service
|
||||
|
||||
use uuid::Uuid;
|
||||
|
||||
/// Config service
|
||||
pub struct ConfigService;
|
||||
|
||||
impl ConfigService {
|
||||
/// Create a new service instance
|
||||
pub fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
/// Apply configuration
|
||||
pub async fn apply(&self, workspace_id: Uuid) -> Result<(), String> {
|
||||
// TODO: Implement
|
||||
tracing::info!("Applying configuration for workspace {}", workspace_id);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
8
crates/nxmesh-master/src/services/mod.rs
Normal file
8
crates/nxmesh-master/src/services/mod.rs
Normal file
@@ -0,0 +1,8 @@
|
||||
//! Business logic services
|
||||
|
||||
pub mod agent_service;
|
||||
pub mod auth_service;
|
||||
pub mod certificate_service;
|
||||
pub mod config_service;
|
||||
pub mod organization_service;
|
||||
pub mod workspace_service;
|
||||
21
crates/nxmesh-master/src/services/organization_service.rs
Normal file
21
crates/nxmesh-master/src/services/organization_service.rs
Normal file
@@ -0,0 +1,21 @@
|
||||
//! Organization service
|
||||
|
||||
use uuid::Uuid;
|
||||
|
||||
/// Organization service
|
||||
pub struct OrganizationService;
|
||||
|
||||
impl OrganizationService {
|
||||
/// Create a new service instance
|
||||
pub fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
/// Create organization
|
||||
pub async fn create(&self, name: &str, slug: &str) -> Result<Uuid, String> {
|
||||
// TODO: Implement
|
||||
let id = Uuid::new_v4();
|
||||
tracing::info!("Creating organization: {} ({}) -> {}", name, slug, id);
|
||||
Ok(id)
|
||||
}
|
||||
}
|
||||
32
crates/nxmesh-master/src/services/workspace_service.rs
Normal file
32
crates/nxmesh-master/src/services/workspace_service.rs
Normal file
@@ -0,0 +1,32 @@
|
||||
//! Workspace service
|
||||
|
||||
use uuid::Uuid;
|
||||
|
||||
/// Workspace service
|
||||
pub struct WorkspaceService;
|
||||
|
||||
impl WorkspaceService {
|
||||
/// Create a new service instance
|
||||
pub fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
/// Create workspace
|
||||
pub async fn create(
|
||||
&self,
|
||||
org_id: Uuid,
|
||||
name: &str,
|
||||
slug: &str,
|
||||
) -> Result<Uuid, String> {
|
||||
// TODO: Implement
|
||||
let id = Uuid::new_v4();
|
||||
tracing::info!(
|
||||
"Creating workspace: {} ({}) in org {} -> {}",
|
||||
name,
|
||||
slug,
|
||||
org_id,
|
||||
id
|
||||
);
|
||||
Ok(id)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user