//! Preference handling for actor store. //! Based on https://github.com/blacksky-algorithms/rsky/blob/main/rsky-pds/src/actor_store/preference/mod.rs //! blacksky-algorithms/rsky is licensed under the Apache License 2.0 //! //! Modified for SQLite backend use crate::models::actor_store::AccountPref; use anyhow::{Result, bail}; use diesel::*; use rsky_lexicon::app::bsky::actor::RefPreferences; use rsky_pds::actor_store::preference::pref_match_namespace; use rsky_pds::actor_store::preference::util::pref_in_scope; use rsky_pds::auth_verifier::AuthScope; pub struct PreferenceReader { pub did: String, pub db: deadpool_diesel::Pool< deadpool_diesel::Manager, deadpool_diesel::sqlite::Object, >, } impl PreferenceReader { pub const fn new( did: String, db: deadpool_diesel::Pool< deadpool_diesel::Manager, deadpool_diesel::sqlite::Object, >, ) -> Self { Self { did, db } } pub async fn get_preferences( &self, namespace: Option, scope: AuthScope, ) -> Result> { use crate::schema::actor_store::account_pref::dsl as AccountPrefSchema; let did = self.did.clone(); self.db .get() .await? .interact(move |conn| { let prefs_res = AccountPrefSchema::account_pref .filter(AccountPrefSchema::did.eq(&did)) .select(AccountPref::as_select()) .order(AccountPrefSchema::id.asc()) .load(conn)?; let account_prefs = prefs_res .into_iter() .filter(|pref| { namespace .as_ref() .is_none_or(|namespace| pref_match_namespace(namespace, &pref.name)) }) .filter(|pref| pref_in_scope(scope.clone(), pref.name.clone())) .map(|pref| { let value_json_res = match pref.value_json { None => bail!("preferences json null for {}", pref.name), Some(value_json) => serde_json::from_str::(&value_json), }; match value_json_res { Err(error) => bail!(error.to_string()), Ok(value_json) => Ok(value_json), } }) .collect::>>()?; Ok(account_prefs) }) .await .expect("Failed to get preferences") } #[tracing::instrument(skip_all)] pub async fn put_preferences( &self, values: Vec, namespace: String, scope: AuthScope, ) -> Result<()> { let did = self.did.clone(); self.db.get().await? .interact(move |conn| { match values .iter() .all(|value| pref_match_namespace(&namespace, &value.get_type())) { false => bail!("Some preferences are not in the {namespace} namespace"), true => { if values .iter().any(|value| !pref_in_scope(scope.clone(), value.get_type())) { tracing::info!( "@LOG: PreferenceReader::put_preferences() debug scope: {:?}, values: {:?}", scope, values ); bail!("Do not have authorization to set preferences."); } // get all current prefs for user and prep new pref rows use crate::schema::actor_store::account_pref::dsl as AccountPrefSchema; let all_prefs = AccountPrefSchema::account_pref .filter(AccountPrefSchema::did.eq(&did)) .select(AccountPref::as_select()) .load(conn)?; let put_prefs = values .into_iter() .map(|value| { Ok(AccountPref { id: 0, name: value.get_type(), value_json: Some(serde_json::to_string(&value)?), }) }) .collect::>>()?; let all_pref_ids_in_namespace = all_prefs .iter() .filter(|pref| pref_match_namespace(&namespace, &pref.name)) .filter(|pref| pref_in_scope(scope.clone(), pref.name.clone())) .map(|pref| pref.id) .collect::>(); // replace all prefs in given namespace if !all_pref_ids_in_namespace.is_empty() { _ = delete(AccountPrefSchema::account_pref) .filter(AccountPrefSchema::id.eq_any(all_pref_ids_in_namespace)) .execute(conn)?; } if !put_prefs.is_empty() { _ = insert_into(AccountPrefSchema::account_pref) .values( put_prefs .into_iter() .map(|pref| { ( AccountPrefSchema::did.eq(&did), AccountPrefSchema::name.eq(pref.name), AccountPrefSchema::valueJson.eq(pref.value_json), ) }) .collect::>(), ) .execute(conn)?; } Ok(()) } } }) .await .expect("Failed to put preferences") } }