use config::ai::AiConfig; use export::AppState; use models::users::user::{self, ProfileVisibility}; use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, Set}; use time::OffsetDateTime; /// Resolve or create the AI bot account in KenOAuth, then project it locally. pub async fn resolve_bot_user(state: &AppState, cfg: &AiConfig) -> Result { let username = cfg.bot_username.trim(); if username.is_empty() { return Err(sea_orm::DbErr::Custom( "ai.bot_username must not be empty".to_string(), )); } ensure_bot( state, username, cfg.bot_display_name.trim(), true, ) .await } pub async fn ensure_bot( state: &AppState, username: &str, displayname: &str, system: bool, ) -> Result { let client = state .kenauth .as_ref() .ok_or_else(|| sea_orm::DbErr::Custom("kenoauth is not configured".to_string()))?; let public = client .ensure_bot_user(username, displayname, system) .await .map_err(|error| sea_orm::DbErr::Custom(error.to_string()))?; upsert_local_bot(state, &public, system).await?; Ok(public.user_id) } pub async fn upsert_local_bot( state: &AppState, public: &kenauth::proto::common::v1::UserPublic, system: bool, ) -> Result { let db = state.database.write(); let now = OffsetDateTime::now_utc(); let lower = public.username.to_lowercase(); let display_name = if public.displayname.trim().is_empty() { public.username.clone() } else { public.displayname.clone() }; let existing = user::Entity::find() .filter(user::Column::LowerName.eq(&lower)) .one(db) .await?; let row = match existing { Some(row) => { let mut active: user::ActiveModel = row.into(); active.uid = Set(public.user_id); active.username = Set(public.username.clone()); active.lower_name = Set(lower); active.display_name = Set(display_name); active.avatar_url = Set(public.avatar_url.clone()); active.is_active = Set(true); active.prohibit_login = Set(true); active.bot = Set(true); active.system = Set(system); active.updated_at = Set(now); active.update(db).await? } None => { user::ActiveModel { uid: Set(public.user_id), username: Set(public.username.clone()), lower_name: Set(lower), display_name: Set(display_name), avatar_url: Set(public.avatar_url.clone()), is_active: Set(true), is_restricted: Set(false), prohibit_login: Set(true), visibility: Set(ProfileVisibility::Public), bot: Set(true), system: Set(system), last_login_at: Set(None), last_login_ip_hash: Set(None), version: Set(1), passwd_hash: Set(None), password_last_changed_at: Set(None), passwd_version: Set(None), totp_encrypted_secret: Set(None), totp_enabled: Set(false), totp_last_used_at: Set(None), totp_last_counter: Set(None), timezone: Set(None), locale: Set(None), language: Set(None), created_at: Set(now), updated_at: Set(now), } .insert(db) .await? } }; Ok(row) }