feat: Add core models and protocol definitions for NxMesh
This commit is contained in:
18
crates/nxmesh-core/Cargo.toml
Normal file
18
crates/nxmesh-core/Cargo.toml
Normal file
@@ -0,0 +1,18 @@
|
||||
[package]
|
||||
name = "nxmesh-core"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
authors.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
thiserror.workspace = true
|
||||
chrono.workspace = true
|
||||
uuid.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
tokio-test.workspace = true
|
||||
43
crates/nxmesh-core/src/error.rs
Normal file
43
crates/nxmesh-core/src/error.rs
Normal file
@@ -0,0 +1,43 @@
|
||||
//! Core error types
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
/// Result type alias for core operations
|
||||
pub type Result<T> = std::result::Result<T, CoreError>;
|
||||
|
||||
/// Core error types used across NxMesh
|
||||
#[derive(Error, Debug, Clone)]
|
||||
pub enum CoreError {
|
||||
#[error("Validation error: {0}")]
|
||||
Validation(String),
|
||||
|
||||
#[error("Invalid configuration: {0}")]
|
||||
InvalidConfig(String),
|
||||
|
||||
#[error("Serialization error: {0}")]
|
||||
Serialization(String),
|
||||
|
||||
#[error("Crypto error: {0}")]
|
||||
Crypto(String),
|
||||
|
||||
#[error("Not found: {0}")]
|
||||
NotFound(String),
|
||||
|
||||
#[error("Permission denied: {0}")]
|
||||
PermissionDenied(String),
|
||||
|
||||
#[error("Internal error: {0}")]
|
||||
Internal(String),
|
||||
}
|
||||
|
||||
impl CoreError {
|
||||
/// Create a validation error
|
||||
pub fn validation<S: Into<String>>(msg: S) -> Self {
|
||||
Self::Validation(msg.into())
|
||||
}
|
||||
|
||||
/// Create a not found error
|
||||
pub fn not_found<S: Into<String>>(resource: S) -> Self {
|
||||
Self::NotFound(resource.into())
|
||||
}
|
||||
}
|
||||
9
crates/nxmesh-core/src/lib.rs
Normal file
9
crates/nxmesh-core/src/lib.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
//! NxMesh Core Library
|
||||
//!
|
||||
//! This crate contains shared types, models, and utilities used by both
|
||||
//! the master and agent components of NxMesh.
|
||||
|
||||
pub mod error;
|
||||
pub mod models;
|
||||
|
||||
pub use error::{CoreError, Result};
|
||||
152
crates/nxmesh-core/src/models/agent.rs
Normal file
152
crates/nxmesh-core/src/models/agent.rs
Normal file
@@ -0,0 +1,152 @@
|
||||
//! Agent model
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use uuid::Uuid;
|
||||
|
||||
/// Agent entity - represents a running nxmesh-agent instance
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Agent {
|
||||
pub id: Uuid,
|
||||
pub workspace_id: Uuid,
|
||||
pub name: String,
|
||||
pub hostname: String,
|
||||
pub ip_address: String,
|
||||
pub version: String,
|
||||
pub state: AgentState,
|
||||
pub deployment_mode: DeploymentMode,
|
||||
pub last_seen_at: DateTime<Utc>,
|
||||
pub capabilities: Vec<String>,
|
||||
pub labels: HashMap<String, String>,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub updated_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
/// Agent connection state
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum AgentState {
|
||||
/// Registered but never connected
|
||||
Pending,
|
||||
/// Connected and healthy
|
||||
Online,
|
||||
/// Disconnected
|
||||
Offline,
|
||||
/// Connected but health checks failing
|
||||
Degraded,
|
||||
/// Manually placed in maintenance mode
|
||||
Maintenance,
|
||||
}
|
||||
|
||||
/// Deployment mode for the agent
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum DeploymentMode {
|
||||
/// Docker sidecar (shares PID namespace with nginx)
|
||||
DockerSidecar,
|
||||
/// Kubernetes sidecar
|
||||
KubernetesSidecar,
|
||||
/// Standalone mode (VM or bare metal)
|
||||
Standalone,
|
||||
}
|
||||
|
||||
/// Health report from agent
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct HealthReport {
|
||||
pub agent_id: Uuid,
|
||||
pub timestamp: DateTime<Utc>,
|
||||
pub nginx_status: NginxStatus,
|
||||
pub system_metrics: SystemMetrics,
|
||||
pub config_checksum: String,
|
||||
pub alerts: Vec<Alert>,
|
||||
}
|
||||
|
||||
/// Nginx process status
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct NginxStatus {
|
||||
pub is_running: bool,
|
||||
pub pid: Option<u32>,
|
||||
pub uptime_seconds: u64,
|
||||
pub active_connections: u32,
|
||||
pub requests_per_second: f64,
|
||||
}
|
||||
|
||||
/// System-level metrics
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct SystemMetrics {
|
||||
pub cpu_percent: f64,
|
||||
pub memory_used_mb: u64,
|
||||
pub memory_total_mb: u64,
|
||||
pub disk_used_gb: u64,
|
||||
pub disk_total_gb: u64,
|
||||
}
|
||||
|
||||
/// Alert from agent
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Alert {
|
||||
pub id: String,
|
||||
pub severity: AlertSeverity,
|
||||
pub message: String,
|
||||
pub timestamp: DateTime<Utc>,
|
||||
}
|
||||
|
||||
/// Alert severity levels
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum AlertSeverity {
|
||||
Info,
|
||||
Warning,
|
||||
Error,
|
||||
Critical,
|
||||
}
|
||||
|
||||
impl Agent {
|
||||
/// Create a new agent registration
|
||||
pub fn new(
|
||||
workspace_id: Uuid,
|
||||
name: impl Into<String>,
|
||||
hostname: impl Into<String>,
|
||||
ip_address: impl Into<String>,
|
||||
version: impl Into<String>,
|
||||
deployment_mode: DeploymentMode,
|
||||
) -> Self {
|
||||
let now = Utc::now();
|
||||
Self {
|
||||
id: Uuid::new_v4(),
|
||||
workspace_id,
|
||||
name: name.into(),
|
||||
hostname: hostname.into(),
|
||||
ip_address: ip_address.into(),
|
||||
version: version.into(),
|
||||
state: AgentState::Pending,
|
||||
deployment_mode,
|
||||
last_seen_at: now,
|
||||
capabilities: Vec::new(),
|
||||
labels: HashMap::new(),
|
||||
created_at: now,
|
||||
updated_at: now,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_agent_creation() {
|
||||
let ws_id = Uuid::new_v4();
|
||||
let agent = Agent::new(
|
||||
ws_id,
|
||||
"web-server-01",
|
||||
"web-01.internal",
|
||||
"10.0.1.10",
|
||||
"0.1.0",
|
||||
DeploymentMode::DockerSidecar,
|
||||
);
|
||||
assert_eq!(agent.name, "web-server-01");
|
||||
assert_eq!(agent.state, AgentState::Pending);
|
||||
assert!(agent.capabilities.is_empty());
|
||||
}
|
||||
}
|
||||
146
crates/nxmesh-core/src/models/certificate.rs
Normal file
146
crates/nxmesh-core/src/models/certificate.rs
Normal file
@@ -0,0 +1,146 @@
|
||||
//! Certificate model
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
/// SSL/TLS Certificate entity
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Certificate {
|
||||
pub id: Uuid,
|
||||
pub workspace_id: Uuid,
|
||||
pub domain: String,
|
||||
pub is_wildcard: bool,
|
||||
pub provider: CertificateProvider,
|
||||
pub status: CertificateStatus,
|
||||
pub issued_at: Option<DateTime<Utc>>,
|
||||
pub expires_at: Option<DateTime<Utc>>,
|
||||
pub auto_renew: bool,
|
||||
pub certificate_pem: Option<String>,
|
||||
pub private_key_pem: Option<String>,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub updated_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
/// Certificate provider
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum CertificateProvider {
|
||||
LetsEncrypt,
|
||||
Custom,
|
||||
}
|
||||
|
||||
/// Certificate status
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum CertificateStatus {
|
||||
Pending,
|
||||
Active,
|
||||
Expired,
|
||||
Error,
|
||||
}
|
||||
|
||||
/// ACME challenge info
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct AcmeChallenge {
|
||||
pub certificate_id: Uuid,
|
||||
pub challenge_type: ChallengeType,
|
||||
pub token: String,
|
||||
pub key_authorization: String,
|
||||
pub status: ChallengeStatus,
|
||||
pub completed_at: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
/// ACME challenge type
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ChallengeType {
|
||||
Http01,
|
||||
Dns01,
|
||||
}
|
||||
|
||||
/// ACME challenge status
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ChallengeStatus {
|
||||
Pending,
|
||||
Processing,
|
||||
Valid,
|
||||
Invalid,
|
||||
}
|
||||
|
||||
impl Certificate {
|
||||
/// Create a new certificate request
|
||||
pub fn new(
|
||||
workspace_id: Uuid,
|
||||
domain: impl Into<String>,
|
||||
provider: CertificateProvider,
|
||||
auto_renew: bool,
|
||||
) -> Self {
|
||||
let now = Utc::now();
|
||||
Self {
|
||||
id: Uuid::new_v4(),
|
||||
workspace_id,
|
||||
domain: domain.into(),
|
||||
is_wildcard: false,
|
||||
provider,
|
||||
status: CertificateStatus::Pending,
|
||||
issued_at: None,
|
||||
expires_at: None,
|
||||
auto_renew,
|
||||
certificate_pem: None,
|
||||
private_key_pem: None,
|
||||
created_at: now,
|
||||
updated_at: now,
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if certificate is expired or expiring soon
|
||||
pub fn is_expiring_soon(&self, days: i64) -> bool {
|
||||
match self.expires_at {
|
||||
Some(expires) => {
|
||||
let threshold = Utc::now() + chrono::Duration::days(days);
|
||||
expires <= threshold
|
||||
}
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_certificate_creation() {
|
||||
let ws_id = Uuid::new_v4();
|
||||
let cert = Certificate::new(
|
||||
ws_id,
|
||||
"example.com",
|
||||
CertificateProvider::LetsEncrypt,
|
||||
true,
|
||||
);
|
||||
assert_eq!(cert.domain, "example.com");
|
||||
assert_eq!(cert.status, CertificateStatus::Pending);
|
||||
assert!(cert.auto_renew);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_certificate_expiring_soon() {
|
||||
let ws_id = Uuid::new_v4();
|
||||
let mut cert = Certificate::new(
|
||||
ws_id,
|
||||
"example.com",
|
||||
CertificateProvider::LetsEncrypt,
|
||||
true,
|
||||
);
|
||||
|
||||
// Not expiring
|
||||
cert.expires_at = Some(Utc::now() + chrono::Duration::days(60));
|
||||
assert!(!cert.is_expiring_soon(30));
|
||||
|
||||
// Expiring soon
|
||||
cert.expires_at = Some(Utc::now() + chrono::Duration::days(20));
|
||||
assert!(cert.is_expiring_soon(30));
|
||||
}
|
||||
}
|
||||
195
crates/nxmesh-core/src/models/config.rs
Normal file
195
crates/nxmesh-core/src/models/config.rs
Normal file
@@ -0,0 +1,195 @@
|
||||
//! Configuration models
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use uuid::Uuid;
|
||||
|
||||
/// Virtual Host (server block) configuration
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct VirtualHost {
|
||||
pub id: Uuid,
|
||||
pub workspace_id: Uuid,
|
||||
pub name: String,
|
||||
pub server_name: String,
|
||||
pub listen_port: u16,
|
||||
pub ssl_enabled: bool,
|
||||
pub ssl_certificate_id: Option<Uuid>,
|
||||
pub locations: Vec<Location>,
|
||||
pub http2_enabled: bool,
|
||||
pub http3_enabled: bool,
|
||||
pub gzip_enabled: bool,
|
||||
pub rate_limiting: Option<RateLimitConfig>,
|
||||
pub target_agents: AgentSelector,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub updated_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
/// Location block configuration
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Location {
|
||||
pub path: String,
|
||||
pub proxy_pass: Option<String>,
|
||||
pub upstream_id: Option<Uuid>,
|
||||
pub root: Option<String>,
|
||||
pub index: Option<String>,
|
||||
pub custom_headers: Vec<Header>,
|
||||
pub rewrite_rules: Vec<RewriteRule>,
|
||||
}
|
||||
|
||||
/// HTTP header
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Header {
|
||||
pub name: String,
|
||||
pub value: String,
|
||||
pub always: bool,
|
||||
}
|
||||
|
||||
/// Rewrite rule
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct RewriteRule {
|
||||
pub pattern: String,
|
||||
pub replacement: String,
|
||||
pub flag: String,
|
||||
}
|
||||
|
||||
/// Rate limiting configuration
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct RateLimitConfig {
|
||||
pub zone: String,
|
||||
pub burst: u32,
|
||||
pub per_minute: Option<u32>,
|
||||
pub per_second: Option<u32>,
|
||||
}
|
||||
|
||||
/// Upstream (backend pool) configuration
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Upstream {
|
||||
pub id: Uuid,
|
||||
pub workspace_id: Uuid,
|
||||
pub name: String,
|
||||
pub algorithm: LoadBalanceAlgorithm,
|
||||
pub servers: Vec<UpstreamServer>,
|
||||
pub health_check: Option<HealthCheckConfig>,
|
||||
pub keepalive_connections: Option<u32>,
|
||||
pub keepalive_timeout: Option<u32>,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub updated_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
/// Upstream server definition
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct UpstreamServer {
|
||||
pub address: String,
|
||||
pub weight: u32,
|
||||
pub backup: bool,
|
||||
pub down: bool,
|
||||
pub max_fails: u32,
|
||||
pub fail_timeout: u32,
|
||||
}
|
||||
|
||||
/// Load balancing algorithm
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum LoadBalanceAlgorithm {
|
||||
RoundRobin,
|
||||
LeastConnections,
|
||||
IpHash,
|
||||
WeightedRoundRobin,
|
||||
}
|
||||
|
||||
/// Health check configuration for upstreams
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct HealthCheckConfig {
|
||||
pub enabled: bool,
|
||||
pub path: String,
|
||||
pub interval: u32,
|
||||
pub timeout: u32,
|
||||
pub healthy_threshold: u32,
|
||||
pub unhealthy_threshold: u32,
|
||||
}
|
||||
|
||||
/// Selector for targeting agents
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(tag = "type", rename_all = "snake_case")]
|
||||
pub enum AgentSelector {
|
||||
/// All agents in the workspace
|
||||
All,
|
||||
/// Agents matching label selector
|
||||
Selector { label_selector: String },
|
||||
/// Specific agent
|
||||
Agent { agent_id: Uuid },
|
||||
}
|
||||
|
||||
/// Configuration scope for distribution
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ConfigScope {
|
||||
/// All agents
|
||||
Global,
|
||||
/// All agents in workspace
|
||||
Workspace,
|
||||
/// Agents with specific label selector
|
||||
AgentGroup(String),
|
||||
/// Single agent
|
||||
Agent(Uuid),
|
||||
}
|
||||
|
||||
/// Configuration version for tracking changes
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ConfigVersion {
|
||||
pub id: Uuid,
|
||||
pub resource_type: String,
|
||||
pub resource_id: Uuid,
|
||||
pub version_number: u64,
|
||||
pub data: serde_json::Value,
|
||||
pub checksum: String,
|
||||
pub created_by: Uuid,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub change_summary: String,
|
||||
}
|
||||
|
||||
/// Configuration update message sent to agents
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ConfigUpdate {
|
||||
pub config_id: String,
|
||||
pub version: u64,
|
||||
pub virtual_hosts: Vec<VirtualHost>,
|
||||
pub upstreams: Vec<Upstream>,
|
||||
pub ssl_certificates: HashMap<String, Certificate>,
|
||||
}
|
||||
|
||||
/// Certificate data in config update
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Certificate {
|
||||
pub id: String,
|
||||
pub domain: String,
|
||||
pub certificate_pem: String,
|
||||
pub private_key_pem: String,
|
||||
pub expires_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
/// Configuration apply status
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ConfigApplyStatus {
|
||||
Pending,
|
||||
Validating,
|
||||
Applying,
|
||||
Success,
|
||||
Failed,
|
||||
RolledBack,
|
||||
}
|
||||
|
||||
impl Default for UpstreamServer {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
address: String::new(),
|
||||
weight: 1,
|
||||
backup: false,
|
||||
down: false,
|
||||
max_fails: 1,
|
||||
fail_timeout: 10,
|
||||
}
|
||||
}
|
||||
}
|
||||
13
crates/nxmesh-core/src/models/mod.rs
Normal file
13
crates/nxmesh-core/src/models/mod.rs
Normal file
@@ -0,0 +1,13 @@
|
||||
//! Shared data models
|
||||
|
||||
pub mod agent;
|
||||
pub mod certificate;
|
||||
pub mod config;
|
||||
pub mod organization;
|
||||
pub mod workspace;
|
||||
|
||||
pub use agent::{Agent, AgentState, DeploymentMode};
|
||||
pub use certificate::{Certificate, CertificateProvider, CertificateStatus};
|
||||
pub use config::{AgentSelector, ConfigScope, Location, Upstream, UpstreamServer, VirtualHost};
|
||||
pub use organization::Organization;
|
||||
pub use workspace::Workspace;
|
||||
62
crates/nxmesh-core/src/models/organization.rs
Normal file
62
crates/nxmesh-core/src/models/organization.rs
Normal file
@@ -0,0 +1,62 @@
|
||||
//! Organization model
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
/// Organization entity
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Organization {
|
||||
pub id: Uuid,
|
||||
pub name: String,
|
||||
pub slug: String,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub updated_at: DateTime<Utc>,
|
||||
pub settings: OrganizationSettings,
|
||||
}
|
||||
|
||||
/// Organization-specific settings
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct OrganizationSettings {
|
||||
pub allow_public_agents: bool,
|
||||
pub max_workspaces: Option<u32>,
|
||||
pub max_agents_per_workspace: Option<u32>,
|
||||
}
|
||||
|
||||
impl Default for OrganizationSettings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
allow_public_agents: false,
|
||||
max_workspaces: None,
|
||||
max_agents_per_workspace: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Organization {
|
||||
/// Create a new organization
|
||||
pub fn new(name: impl Into<String>, slug: impl Into<String>) -> Self {
|
||||
let now = Utc::now();
|
||||
Self {
|
||||
id: Uuid::new_v4(),
|
||||
name: name.into(),
|
||||
slug: slug.into(),
|
||||
created_at: now,
|
||||
updated_at: now,
|
||||
settings: OrganizationSettings::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_organization_creation() {
|
||||
let org = Organization::new("Acme Corp", "acme-corp");
|
||||
assert_eq!(org.name, "Acme Corp");
|
||||
assert_eq!(org.slug, "acme-corp");
|
||||
assert!(!org.settings.allow_public_agents);
|
||||
}
|
||||
}
|
||||
49
crates/nxmesh-core/src/models/workspace.rs
Normal file
49
crates/nxmesh-core/src/models/workspace.rs
Normal file
@@ -0,0 +1,49 @@
|
||||
//! Workspace model
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
/// Workspace entity - isolated resource container within an organization
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Workspace {
|
||||
pub id: Uuid,
|
||||
pub organization_id: Uuid,
|
||||
pub name: String,
|
||||
pub slug: String,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub updated_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
impl Workspace {
|
||||
/// Create a new workspace
|
||||
pub fn new(
|
||||
organization_id: Uuid,
|
||||
name: impl Into<String>,
|
||||
slug: impl Into<String>,
|
||||
) -> Self {
|
||||
let now = Utc::now();
|
||||
Self {
|
||||
id: Uuid::new_v4(),
|
||||
organization_id,
|
||||
name: name.into(),
|
||||
slug: slug.into(),
|
||||
created_at: now,
|
||||
updated_at: now,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_workspace_creation() {
|
||||
let org_id = Uuid::new_v4();
|
||||
let ws = Workspace::new(org_id, "Production", "production");
|
||||
assert_eq!(ws.name, "Production");
|
||||
assert_eq!(ws.slug, "production");
|
||||
assert_eq!(ws.organization_id, org_id);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user