use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use sqlx::{FromRow, PgPool}; use uuid::Uuid; #[derive(Debug, Serialize, Deserialize, FromRow)] pub struct OnboardingConfigListItem { pub id: Uuid, pub role_id: Uuid, pub role_key: String, pub version: i32, pub is_active: bool, pub updated_at: DateTime, } #[derive(Debug, Serialize, Deserialize, FromRow)] pub struct OnboardingConfig { pub id: Uuid, pub role_id: Uuid, pub schema_json: serde_json::Value, pub version: i32, pub is_active: bool, pub updated_at: DateTime, } #[derive(Debug, Serialize, Deserialize)] pub struct CreateOnboardingConfigPayload { pub role_id: Uuid, pub schema_json: serde_json::Value, } #[derive(Debug, Serialize, Deserialize, FromRow)] pub struct DashboardConfigListItem { pub id: Uuid, pub role_id: Uuid, pub role_key: String, pub audience: String, pub version: i32, pub is_active: bool, pub updated_at: DateTime, } #[derive(Debug, Serialize, Deserialize, FromRow)] pub struct DashboardConfig { pub id: Uuid, pub role_id: Uuid, pub audience: String, pub config_json: serde_json::Value, pub version: i32, pub is_active: bool, pub updated_at: DateTime, } #[derive(Debug, Serialize, Deserialize)] pub struct CreateDashboardConfigPayload { pub role_id: Uuid, pub audience: String, pub config_json: serde_json::Value, } #[derive(Debug, Serialize, Deserialize, FromRow)] pub struct RuntimeConfig { pub id: Uuid, pub role_id: Uuid, pub config_json: serde_json::Value, pub version: i32, pub is_active: bool, pub updated_at: DateTime, } #[derive(Debug, Serialize, Deserialize)] pub struct CreateRuntimeConfigPayload { pub role_id: Uuid, pub config_json: serde_json::Value, } pub struct ConfigRepository; impl ConfigRepository { pub async fn create_onboarding_config( pool: &PgPool, payload: CreateOnboardingConfigPayload, ) -> Result { // Soft-disable previous active configs for this role sqlx::query!( r#" UPDATE onboarding_configs SET is_active = false WHERE role_id = $1 AND is_active = true "#, payload.role_id ) .execute(pool) .await?; // Insert new config let config = sqlx::query_as!( OnboardingConfig, r#" INSERT INTO onboarding_configs (role_id, schema_json, version, is_active) VALUES ( $1, $2, COALESCE((SELECT MAX(version) FROM onboarding_configs WHERE role_id = $1), 0) + 1, true ) RETURNING id, role_id, schema_json, version, is_active, updated_at "#, payload.role_id, payload.schema_json ) .fetch_one(pool) .await?; Ok(config) } pub async fn get_active_onboarding_config( pool: &PgPool, role_id: Uuid, ) -> Result { let config = sqlx::query_as!( OnboardingConfig, r#" SELECT id, role_id, schema_json, version, is_active, updated_at FROM onboarding_configs WHERE role_id = $1 AND is_active = true "#, role_id ) .fetch_one(pool) .await?; Ok(config) } pub async fn get_all_onboarding_configs( pool: &PgPool, ) -> Result, sqlx::Error> { let configs = sqlx::query_as!( OnboardingConfigListItem, r#" SELECT c.id, c.role_id, r.key as role_key, c.version, c.is_active, c.updated_at FROM onboarding_configs c JOIN roles r ON c.role_id = r.id ORDER BY c.updated_at DESC "# ) .fetch_all(pool) .await?; Ok(configs) } pub async fn get_active_onboarding_by_role_key( pool: &PgPool, role_key: &str, ) -> Result { let config = sqlx::query_as!( OnboardingConfig, r#" SELECT c.id, c.role_id, c.schema_json, c.version, c.is_active, c.updated_at FROM onboarding_configs c JOIN roles r ON c.role_id = r.id WHERE r.key = $1 AND c.is_active = true "#, role_key.to_uppercase() ) .fetch_one(pool) .await?; Ok(config) } pub async fn create_dashboard_config( pool: &PgPool, payload: CreateDashboardConfigPayload, ) -> Result { // Soft-disable previous active configs for this role sqlx::query!( r#" UPDATE dashboard_configs SET is_active = false WHERE role_id = $1 AND audience = $2::text AND is_active = true "#, payload.role_id, payload.audience ) .execute(pool) .await?; // Insert new config let config = sqlx::query_as!( DashboardConfig, r#" INSERT INTO dashboard_configs (role_id, audience, config_json, version, is_active) VALUES ( $1, $2::text, $3, COALESCE((SELECT MAX(version) FROM dashboard_configs WHERE role_id = $1 AND audience = $2::text), 0) + 1, true ) RETURNING id, role_id, audience, config_json, version, is_active, updated_at "#, payload.role_id, payload.audience, payload.config_json ) .fetch_one(pool) .await?; Ok(config) } pub async fn get_active_dashboard_config( pool: &PgPool, role_id: Uuid, audience: &str, ) -> Result { let config = sqlx::query_as!( DashboardConfig, r#" SELECT id, role_id, audience, config_json, version, is_active, updated_at FROM dashboard_configs WHERE role_id = $1 AND audience = $2 AND is_active = true "#, role_id, audience ) .fetch_one(pool) .await?; Ok(config) } pub async fn get_all_dashboard_configs( pool: &PgPool, ) -> Result, sqlx::Error> { let configs = sqlx::query_as!( DashboardConfigListItem, r#" SELECT c.id, c.role_id, r.key as role_key, c.audience, c.version, c.is_active, c.updated_at FROM dashboard_configs c JOIN roles r ON c.role_id = r.id ORDER BY c.updated_at DESC "# ) .fetch_all(pool) .await?; Ok(configs) } pub async fn get_active_dashboard_by_role_key( pool: &PgPool, role_key: &str, audience: &str, ) -> Result { let config = sqlx::query_as!( DashboardConfig, r#" SELECT c.id, c.role_id, c.audience, c.config_json, c.version, c.is_active, c.updated_at FROM dashboard_configs c JOIN roles r ON c.role_id = r.id WHERE r.key = $1 AND c.audience = $2 AND c.is_active = true "#, role_key.to_uppercase(), audience ) .fetch_one(pool) .await?; Ok(config) } pub async fn create_runtime_config( pool: &PgPool, payload: CreateRuntimeConfigPayload, ) -> Result { // Soft-disable previous active configs for this role sqlx::query!( r#" UPDATE runtime_configs SET is_active = false WHERE role_id = $1 AND is_active = true "#, payload.role_id ) .execute(pool) .await?; // Insert new config let config = sqlx::query_as!( RuntimeConfig, r#" INSERT INTO runtime_configs (role_id, config_json, version, is_active) VALUES ( $1, $2, COALESCE((SELECT MAX(version) FROM runtime_configs WHERE role_id = $1), 0) + 1, true ) RETURNING id, role_id, config_json, version, is_active, updated_at "#, payload.role_id, payload.config_json ) .fetch_one(pool) .await?; Ok(config) } pub async fn get_active_runtime_config( pool: &PgPool, role_id: Uuid, ) -> Result { let config = sqlx::query_as!( RuntimeConfig, r#" SELECT id, role_id, config_json, version, is_active, updated_at FROM runtime_configs WHERE role_id = $1 AND is_active = true "#, role_id ) .fetch_one(pool) .await?; Ok(config) } pub async fn get_active_runtime_by_role_key( pool: &PgPool, role_key: &str, ) -> Result { let config = sqlx::query_as!( RuntimeConfig, r#" SELECT rc.id, rc.role_id, rc.config_json, rc.version, rc.is_active, rc.updated_at FROM runtime_configs rc JOIN roles r ON rc.role_id = r.id WHERE r.key = $1 AND rc.is_active = true "#, role_key.to_uppercase() ) .fetch_one(pool) .await?; Ok(config) } }