//! Service layer for persistent storage of keys, runners, and jobs //! //! This module provides database/storage services for the supervisor. //! Currently uses in-memory storage, but designed to be easily extended //! to use Redis, PostgreSQL, or other persistent storage backends. use crate::auth::{ApiKey, ApiKeyScope}; use hero_job::Job; use std::collections::HashMap; use std::sync::Arc; use tokio::sync::Mutex; use serde::{Deserialize, Serialize}; /// Service for managing API keys #[derive(Debug, Clone)] pub struct ApiKeyService { store: Arc>>, } impl ApiKeyService { /// Create a new API key service pub fn new() -> Self { Self { store: Arc::new(Mutex::new(HashMap::new())), } } /// Store an API key pub async fn store(&self, key: ApiKey) -> Result<(), String> { let mut store = self.store.lock().await; store.insert(key.key.clone(), key); Ok(()) } /// Get an API key by its key string pub async fn get(&self, key: &str) -> Option { let store = self.store.lock().await; store.get(key).cloned() } /// List all API keys pub async fn list(&self) -> Vec { let store = self.store.lock().await; store.values().cloned().collect() } /// Remove an API key pub async fn remove(&self, key: &str) -> Option { let mut store = self.store.lock().await; store.remove(key) } /// Count API keys by scope pub async fn count_by_scope(&self, scope: ApiKeyScope) -> usize { let store = self.store.lock().await; store.values().filter(|k| k.scope == scope).count() } /// Clear all API keys (for testing) pub async fn clear(&self) { let mut store = self.store.lock().await; store.clear(); } } impl Default for ApiKeyService { fn default() -> Self { Self::new() } } /// Service for managing runners #[derive(Debug, Clone)] pub struct RunnerService { store: Arc>>, } /// Metadata about a runner for storage #[derive(Debug, Clone, Serialize, Deserialize)] pub struct RunnerMetadata { pub id: String, pub name: String, pub queue: String, pub registered_at: String, pub registered_by: String, // API key name that registered this runner } impl RunnerService { /// Create a new runner service pub fn new() -> Self { Self { store: Arc::new(Mutex::new(HashMap::new())), } } /// Store runner metadata pub async fn store(&self, metadata: RunnerMetadata) -> Result<(), String> { let mut store = self.store.lock().await; store.insert(metadata.id.clone(), metadata); Ok(()) } /// Get runner metadata by ID pub async fn get(&self, id: &str) -> Option { let store = self.store.lock().await; store.get(id).cloned() } /// List all runners pub async fn list(&self) -> Vec { let store = self.store.lock().await; store.values().cloned().collect() } /// Remove a runner pub async fn remove(&self, id: &str) -> Option { let mut store = self.store.lock().await; store.remove(id) } /// Count total runners pub async fn count(&self) -> usize { let store = self.store.lock().await; store.len() } /// Clear all runners (for testing) pub async fn clear(&self) { let mut store = self.store.lock().await; store.clear(); } } impl Default for RunnerService { fn default() -> Self { Self::new() } } /// Service for managing jobs #[derive(Debug, Clone)] pub struct JobService { store: Arc>>, } /// Metadata about a job for storage #[derive(Debug, Clone, Serialize, Deserialize)] pub struct JobMetadata { pub job_id: String, pub runner: String, pub created_at: String, pub created_by: String, // API key name that created this job pub status: String, pub job: Job, } impl JobService { /// Create a new job service pub fn new() -> Self { Self { store: Arc::new(Mutex::new(HashMap::new())), } } /// Store job metadata pub async fn store(&self, metadata: JobMetadata) -> Result<(), String> { let mut store = self.store.lock().await; store.insert(metadata.job_id.clone(), metadata); Ok(()) } /// Get job metadata by ID pub async fn get(&self, job_id: &str) -> Option { let store = self.store.lock().await; store.get(job_id).cloned() } /// List all jobs pub async fn list(&self) -> Vec { let store = self.store.lock().await; store.values().cloned().collect() } /// List jobs by runner pub async fn list_by_runner(&self, runner: &str) -> Vec { let store = self.store.lock().await; store.values() .filter(|j| j.runner == runner) .cloned() .collect() } /// List jobs by creator (API key name) pub async fn list_by_creator(&self, creator: &str) -> Vec { let store = self.store.lock().await; store.values() .filter(|j| j.created_by == creator) .cloned() .collect() } /// Update job status pub async fn update_status(&self, job_id: &str, status: String) -> Result<(), String> { let mut store = self.store.lock().await; if let Some(metadata) = store.get_mut(job_id) { metadata.status = status; Ok(()) } else { Err(format!("Job not found: {}", job_id)) } } /// Remove a job pub async fn remove(&self, job_id: &str) -> Option { let mut store = self.store.lock().await; store.remove(job_id) } /// Count total jobs pub async fn count(&self) -> usize { let store = self.store.lock().await; store.len() } /// Clear all jobs (for testing) pub async fn clear(&self) { let mut store = self.store.lock().await; store.clear(); } } impl Default for JobService { fn default() -> Self { Self::new() } } /// Combined service container for all storage services #[derive(Debug, Clone)] pub struct Services { pub api_keys: ApiKeyService, pub runners: RunnerService, pub jobs: JobService, } impl Services { /// Create a new services container pub fn new() -> Self { Self { api_keys: ApiKeyService::new(), runners: RunnerService::new(), jobs: JobService::new(), } } /// Clear all data (for testing) pub async fn clear_all(&self) { self.api_keys.clear().await; self.runners.clear().await; self.jobs.clear().await; } } impl Default for Services { fn default() -> Self { Self::new() } }