use std::collections::HashMap;
use std::sync::Arc;
use parking_lot::RwLock;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Tenant {
pub id: String,
pub name: String,
pub plan: TenantPlan,
pub max_crawls: u64,
pub max_pages: usize,
pub max_concurrent: usize,
pub rate_limit: u32,
pub features: Vec<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum TenantPlan {
Free,
Pro,
Enterprise,
}
pub struct TenantManager {
tenants: Arc<RwLock<HashMap<String, Tenant>>>,
}
impl TenantManager {
#[must_use]
pub fn new() -> Self {
Self {
tenants: Arc::new(RwLock::new(HashMap::new())),
}
}
pub fn add_tenant(&self, tenant: Tenant) {
let mut tenants = self.tenants.write();
tenants.insert(tenant.id.clone(), tenant);
}
#[must_use]
pub fn get_tenant(&self, id: &str) -> Option<Tenant> {
self.tenants.read().get(id).cloned()
}
#[must_use]
pub fn list_tenants(&self) -> Vec<Tenant> {
self.tenants.read().values().cloned().collect()
}
pub fn remove_tenant(&self, id: &str) -> bool {
self.tenants.write().remove(id).is_some()
}
#[must_use]
pub fn count(&self) -> usize {
self.tenants.read().len()
}
}
impl Default for TenantManager {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Role {
pub id: String,
pub name: String,
pub permissions: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Permission {
CrawlCreate,
CrawlRead,
CrawlDelete,
ApiKeyManage,
AnalyticsView,
UserManage,
TenantManage,
AuditView,
BillingManage,
}
impl Permission {
#[must_use]
pub fn as_str(&self) -> &'static str {
match self {
Permission::CrawlCreate => "crawl:create",
Permission::CrawlRead => "crawl:read",
Permission::CrawlDelete => "crawl:delete",
Permission::ApiKeyManage => "apikey:manage",
Permission::AnalyticsView => "analytics:view",
Permission::UserManage => "user:manage",
Permission::TenantManage => "tenant:manage",
Permission::AuditView => "audit:view",
Permission::BillingManage => "billing:manage",
}
}
pub fn parse(s: &str) -> Option<Self> {
match s {
"crawl:create" => Some(Permission::CrawlCreate),
"crawl:read" => Some(Permission::CrawlRead),
"crawl:delete" => Some(Permission::CrawlDelete),
"apikey:manage" => Some(Permission::ApiKeyManage),
"analytics:view" => Some(Permission::AnalyticsView),
"user:manage" => Some(Permission::UserManage),
"tenant:manage" => Some(Permission::TenantManage),
"audit:view" => Some(Permission::AuditView),
"billing:manage" => Some(Permission::BillingManage),
_ => None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct User {
pub id: String,
pub email: String,
pub name: String,
pub tenant_id: String,
pub roles: Vec<String>,
pub active: bool,
}
pub struct RbacManager {
roles: Arc<RwLock<HashMap<String, Role>>>,
users: Arc<RwLock<HashMap<String, User>>>,
}
impl RbacManager {
#[must_use]
pub fn new() -> Self {
let mut roles = HashMap::new();
roles.insert(
"admin".to_string(),
Role {
id: "admin".to_string(),
name: "Administrator".to_string(),
permissions: Permission::iter().map(|p| p.as_str().to_string()).collect(),
},
);
roles.insert(
"user".to_string(),
Role {
id: "user".to_string(),
name: "User".to_string(),
permissions: vec![
Permission::CrawlCreate.as_str().to_string(),
Permission::CrawlRead.as_str().to_string(),
Permission::AnalyticsView.as_str().to_string(),
],
},
);
roles.insert(
"viewer".to_string(),
Role {
id: "viewer".to_string(),
name: "Viewer".to_string(),
permissions: vec![
Permission::CrawlRead.as_str().to_string(),
Permission::AnalyticsView.as_str().to_string(),
],
},
);
Self {
roles: Arc::new(RwLock::new(roles)),
users: Arc::new(RwLock::new(HashMap::new())),
}
}
pub fn add_role(&self, role: Role) {
let mut roles = self.roles.write();
roles.insert(role.id.clone(), role);
}
#[must_use]
pub fn get_role(&self, id: &str) -> Option<Role> {
self.roles.read().get(id).cloned()
}
pub fn add_user(&self, user: User) {
let mut users = self.users.write();
users.insert(user.id.clone(), user);
}
#[must_use]
pub fn get_user(&self, id: &str) -> Option<User> {
self.users.read().get(id).cloned()
}
#[must_use]
pub fn has_permission(&self, user_id: &str, permission: &Permission) -> bool {
let users = self.users.read();
let roles = self.roles.read();
if let Some(user) = users.get(user_id) {
if !user.active {
return false;
}
for role_id in &user.roles {
if let Some(role) = roles.get(role_id) {
if role.permissions.contains(&permission.as_str().to_string()) {
return true;
}
}
}
}
false
}
#[must_use]
pub fn get_permissions(&self, user_id: &str) -> Vec<String> {
let users = self.users.read();
let roles = self.roles.read();
let mut permissions = Vec::new();
if let Some(user) = users.get(user_id) {
if !user.active {
return permissions;
}
for role_id in &user.roles {
if let Some(role) = roles.get(role_id) {
permissions.extend(role.permissions.clone());
}
}
}
permissions.sort();
permissions.dedup();
permissions
}
}
impl Default for RbacManager {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SsoConfig {
pub provider: SsoProvider,
pub provider_url: String,
pub client_id: String,
pub client_secret: String,
pub callback_url: String,
pub enabled_domains: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum SsoProvider {
Saml,
Oidc,
}
pub struct SsoManager {
configs: Arc<RwLock<Vec<SsoConfig>>>,
}
impl SsoManager {
#[must_use]
pub fn new() -> Self {
Self {
configs: Arc::new(RwLock::new(Vec::new())),
}
}
pub fn add_config(&self, config: SsoConfig) {
let mut configs = self.configs.write();
configs.push(config);
}
#[must_use]
pub fn get_config_for_domain(&self, domain: &str) -> Option<SsoConfig> {
self.configs
.read()
.iter()
.find(|c| c.enabled_domains.contains(&domain.to_string()))
.cloned()
}
#[must_use]
pub fn list_configs(&self) -> Vec<SsoConfig> {
self.configs.read().clone()
}
}
impl Default for SsoManager {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SlaConfig {
pub uptime_target: f64,
pub response_time_target: u64,
pub error_rate_target: f64,
pub alert_thresholds: SlaThresholds,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SlaThresholds {
pub warning: f64,
pub critical: f64,
}
impl Default for SlaConfig {
fn default() -> Self {
Self {
uptime_target: 99.9,
response_time_target: 500,
error_rate_target: 1.0,
alert_thresholds: SlaThresholds {
warning: 0.95,
critical: 0.90,
},
}
}
}
pub struct SlaMonitor {
config: SlaConfig,
metrics: Arc<RwLock<SlaMetrics>>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct SlaMetrics {
pub total_requests: u64,
pub successful_requests: u64,
pub failed_requests: u64,
pub total_response_time: u64,
pub uptime: f64,
pub avg_response_time: f64,
pub error_rate: f64,
}
impl SlaMonitor {
#[must_use]
pub fn new(config: SlaConfig) -> Self {
Self {
config,
metrics: Arc::new(RwLock::new(SlaMetrics::default())),
}
}
#[must_use]
pub fn with_default_config() -> Self {
Self::new(SlaConfig::default())
}
pub fn record_success(&self, response_time_ms: u64) {
let mut metrics = self.metrics.write();
metrics.total_requests += 1;
metrics.successful_requests += 1;
metrics.total_response_time += response_time_ms;
self.update_metrics(&mut metrics);
}
pub fn record_failure(&self) {
let mut metrics = self.metrics.write();
metrics.total_requests += 1;
metrics.failed_requests += 1;
self.update_metrics(&mut metrics);
}
fn update_metrics(&self, metrics: &mut SlaMetrics) {
if metrics.total_requests > 0 {
metrics.error_rate =
(metrics.failed_requests as f64 / metrics.total_requests as f64) * 100.0;
metrics.avg_response_time =
metrics.total_response_time as f64 / metrics.total_requests as f64;
}
}
#[must_use]
pub fn metrics(&self) -> SlaMetrics {
self.metrics.read().clone()
}
#[must_use]
pub fn is_sla_met(&self) -> bool {
let metrics = self.metrics.read();
metrics.error_rate <= self.config.error_rate_target
&& metrics.avg_response_time <= self.config.response_time_target as f64
}
#[must_use]
pub fn status(&self) -> SlaStatus {
let metrics = self.metrics.read();
let error_rate_ratio = metrics.error_rate / self.config.error_rate_target;
let response_time_ratio =
metrics.avg_response_time / self.config.response_time_target as f64;
let worst_ratio = error_rate_ratio.max(response_time_ratio);
if worst_ratio <= self.config.alert_thresholds.critical {
SlaStatus::Critical
} else if worst_ratio <= self.config.alert_thresholds.warning {
SlaStatus::Warning
} else {
SlaStatus::Healthy
}
}
#[must_use]
pub fn config(&self) -> &SlaConfig {
&self.config
}
}
impl Default for SlaMonitor {
fn default() -> Self {
Self::with_default_config()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum SlaStatus {
Healthy,
Warning,
Critical,
}
trait PermissionIter {
fn iter() -> Box<dyn Iterator<Item = Permission>>;
}
impl PermissionIter for Permission {
fn iter() -> Box<dyn Iterator<Item = Permission>> {
Box::new(
vec![
Permission::CrawlCreate,
Permission::CrawlRead,
Permission::CrawlDelete,
Permission::ApiKeyManage,
Permission::AnalyticsView,
Permission::UserManage,
Permission::TenantManage,
Permission::AuditView,
Permission::BillingManage,
]
.into_iter(),
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tenant_manager() {
let manager = TenantManager::new();
let tenant = Tenant {
id: "tenant1".to_string(),
name: "Acme Corp".to_string(),
plan: TenantPlan::Enterprise,
max_crawls: 1000,
max_pages: 10000,
max_concurrent: 10,
rate_limit: 60,
features: vec!["backlinks".to_string(), "rum".to_string()],
};
manager.add_tenant(tenant);
assert_eq!(manager.count(), 1);
assert!(manager.get_tenant("tenant1").is_some());
}
#[test]
fn test_rbac_manager() {
let manager = RbacManager::new();
let user = User {
id: "user1".to_string(),
email: "test@example.com".to_string(),
name: "Test User".to_string(),
tenant_id: "tenant1".to_string(),
roles: vec!["admin".to_string()],
active: true,
};
manager.add_user(user);
assert!(manager.has_permission("user1", &Permission::CrawlCreate));
assert!(manager.has_permission("user1", &Permission::TenantManage));
assert!(manager.has_permission("user1", &Permission::BillingManage));
assert!(!manager.has_permission("nonexistent", &Permission::CrawlCreate));
}
#[test]
fn test_sla_monitor() {
let monitor = SlaMonitor::with_default_config();
for _ in 0..90 {
monitor.record_success(100);
}
for _ in 0..10 {
monitor.record_failure();
}
let metrics = monitor.metrics();
assert_eq!(metrics.total_requests, 100);
assert_eq!(metrics.successful_requests, 90);
assert_eq!(metrics.failed_requests, 10);
assert!((metrics.error_rate - 10.0).abs() < 0.01);
}
}