telegram bot user isolation, config store, user context channels
This commit is contained in:
@@ -2,13 +2,16 @@ use std::sync::Arc;
|
||||
|
||||
use sqlx::SqlitePool;
|
||||
|
||||
use core_api::system_bus::{SystemEvent, SystemEventBus};
|
||||
|
||||
pub struct GlobalConfigManager {
|
||||
pool: Arc<SqlitePool>,
|
||||
pool: Arc<SqlitePool>,
|
||||
system_bus: Arc<SystemEventBus>,
|
||||
}
|
||||
|
||||
impl GlobalConfigManager {
|
||||
pub fn new(pool: Arc<SqlitePool>) -> Self {
|
||||
Self { pool }
|
||||
pub fn new(pool: Arc<SqlitePool>, system_bus: Arc<SystemEventBus>) -> Self {
|
||||
Self { pool, system_bus }
|
||||
}
|
||||
|
||||
pub async fn get(&self, key: &str) -> anyhow::Result<Option<String>> {
|
||||
@@ -19,7 +22,16 @@ impl GlobalConfigManager {
|
||||
Ok(row.map(|(v,)| v))
|
||||
}
|
||||
|
||||
/// Sets a config key and emits [`SystemEvent::ConfigKeyUpdated`] on the
|
||||
/// system bus when the value actually changes. No-op (no write, no event)
|
||||
/// when the new value equals the current one.
|
||||
pub async fn set(&self, key: &str, value: &str) -> anyhow::Result<()> {
|
||||
let old_value = self.get(key).await?;
|
||||
|
||||
if old_value.as_deref() == Some(value) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
sqlx::query(
|
||||
"INSERT INTO config (key, value, updated_at) VALUES (?, ?, datetime('now'))
|
||||
ON CONFLICT(key) DO UPDATE SET
|
||||
@@ -30,6 +42,13 @@ impl GlobalConfigManager {
|
||||
.bind(value)
|
||||
.execute(&*self.pool)
|
||||
.await?;
|
||||
|
||||
self.system_bus.send(SystemEvent::ConfigKeyUpdated {
|
||||
key: key.to_string(),
|
||||
old_value,
|
||||
new_value: value.to_string(),
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -41,3 +60,13 @@ impl GlobalConfigManager {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl core_api::config_api::ConfigApi for GlobalConfigManager {
|
||||
async fn get(&self, key: &str) -> anyhow::Result<Option<String>> {
|
||||
GlobalConfigManager::get(self, key).await
|
||||
}
|
||||
async fn set(&self, key: &str, value: &str) -> anyhow::Result<()> {
|
||||
GlobalConfigManager::set(self, key, value).await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,6 +97,7 @@ impl PluginManager {
|
||||
|
||||
Ok(PluginContext {
|
||||
command: Arc::clone(skald.command_manager()) as _,
|
||||
config: Arc::clone(skald.config()) as Arc<dyn core_api::config_api::ConfigApi>,
|
||||
db: Arc::clone(skald.db()),
|
||||
secrets: Arc::clone(skald.secrets()) as _,
|
||||
transcribe: Arc::clone(skald.transcribe_manager()) as _,
|
||||
@@ -107,6 +108,7 @@ impl PluginManager {
|
||||
api_provider_registry: Arc::clone(skald.provider_registry()) as _,
|
||||
location: Arc::clone(skald.location_manager()) as _,
|
||||
system_bus: Arc::clone(skald.system_bus()),
|
||||
user_channel: self.skald()? as Arc<dyn core_api::user_channel::UserChannelApi>,
|
||||
web_port,
|
||||
remote_slot: Arc::clone(skald.remote()),
|
||||
router_factory,
|
||||
|
||||
@@ -16,6 +16,7 @@ use tokio_util::sync::CancellationToken;
|
||||
|
||||
use core_api::remote::RemoteAccess;
|
||||
use core_api::system_bus::SystemEventBus;
|
||||
use core_api::user_channel::UserChannelApi;
|
||||
|
||||
use crate::approval::ApprovalManager;
|
||||
use crate::chat_event_bus::ChatEventBus;
|
||||
@@ -112,3 +113,15 @@ impl Skald {
|
||||
pub fn location_manager(&self) -> &Arc<LocationManager> { &self.infra.location_manager }
|
||||
pub fn remote(&self) -> &Arc<RwLock<Option<Arc<dyn RemoteAccess>>>> { &self.infra.remote }
|
||||
}
|
||||
|
||||
// ── UserChannelApi ────────────────────────────────────────────────────────────
|
||||
|
||||
use super::user_context::UserContextHandle;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl UserChannelApi for Skald {
|
||||
async fn resolve_user(&self, user_id: &str) -> Option<std::sync::Arc<dyn core_api::user_channel::UserChannelHandle>> {
|
||||
let ctx = self.user_context(user_id).await?;
|
||||
Some(std::sync::Arc::new(UserContextHandle::new(ctx)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,14 +45,14 @@ pub(super) struct Runtime {
|
||||
impl Runtime {
|
||||
/// Wires the cross-cutting primitives. Infallible.
|
||||
pub(super) fn bootstrap(pool: Arc<SqlitePool>) -> Self {
|
||||
let config = Arc::new(GlobalConfigManager::new(Arc::clone(&pool)));
|
||||
let system_bus = Arc::new(SystemEventBus::new());
|
||||
info!("system event bus ready");
|
||||
|
||||
let config = Arc::new(GlobalConfigManager::new(Arc::clone(&pool), Arc::clone(&system_bus)));
|
||||
|
||||
let users = Arc::new(UserManager::new(Arc::clone(&pool)));
|
||||
let sessions = Arc::new(SessionStore::new(Arc::clone(&users)));
|
||||
|
||||
let system_bus = Arc::new(SystemEventBus::new());
|
||||
info!("system event bus ready");
|
||||
|
||||
let event_bus = Arc::new(ChatEventBus::new());
|
||||
info!("chat event bus ready");
|
||||
|
||||
|
||||
@@ -29,8 +29,11 @@ use sqlx::SqlitePool;
|
||||
use tokio::sync::{broadcast, Mutex};
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
use core_api::approval::ApprovalApi;
|
||||
use core_api::chat_hub::ChatHubApi;
|
||||
use core_api::events::GlobalEvent;
|
||||
use core_api::system_bus::SystemEventBus;
|
||||
use core_api::user_channel::UserChannelHandle;
|
||||
|
||||
use crate::approval::ApprovalManager;
|
||||
use crate::chat_event_bus::ChatEventBus;
|
||||
@@ -257,3 +260,38 @@ impl UserContextRegistry {
|
||||
Ok(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
// ── UserChannelHandle impl ────────────────────────────────────────────────────
|
||||
|
||||
/// Concrete [`UserChannelHandle`] wrapping a live [`UserContext`].
|
||||
///
|
||||
/// Constructed by [`Skald`](super::Skald) when resolving a user for a channel
|
||||
/// plugin. The concrete type stays private — callers receive
|
||||
/// `Arc<dyn UserChannelHandle>`.
|
||||
pub(super) struct UserContextHandle {
|
||||
ctx: Arc<UserContext>,
|
||||
}
|
||||
|
||||
impl UserContextHandle {
|
||||
pub(super) fn new(ctx: Arc<UserContext>) -> Self {
|
||||
Self { ctx }
|
||||
}
|
||||
}
|
||||
|
||||
impl UserChannelHandle for UserContextHandle {
|
||||
fn user_id(&self) -> &str {
|
||||
&self.ctx.user_id
|
||||
}
|
||||
|
||||
fn chat_hub(&self) -> Arc<dyn ChatHubApi> {
|
||||
Arc::clone(&self.ctx.chat_hub) as Arc<dyn ChatHubApi>
|
||||
}
|
||||
|
||||
fn approval(&self) -> Arc<dyn ApprovalApi> {
|
||||
Arc::clone(&self.ctx.approval) as Arc<dyn ApprovalApi>
|
||||
}
|
||||
|
||||
fn subscribe(&self) -> broadcast::Receiver<GlobalEvent> {
|
||||
self.ctx.global_tx.subscribe()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user