Alternative ATProto PDS implementation
1//! Preference handling for actor store.
2//! Based on https://github.com/blacksky-algorithms/rsky/blob/main/rsky-pds/src/actor_store/preference/mod.rs
3//! blacksky-algorithms/rsky is licensed under the Apache License 2.0
4//!
5//! Modified for SQLite backend
6
7use crate::models::actor_store::AccountPref;
8use anyhow::{Result, bail};
9use diesel::*;
10use rsky_lexicon::app::bsky::actor::RefPreferences;
11use rsky_pds::actor_store::preference::pref_match_namespace;
12use rsky_pds::actor_store::preference::util::pref_in_scope;
13use rsky_pds::auth_verifier::AuthScope;
14
15pub struct PreferenceReader {
16 pub did: String,
17 pub db: deadpool_diesel::Pool<
18 deadpool_diesel::Manager<SqliteConnection>,
19 deadpool_diesel::sqlite::Object,
20 >,
21}
22
23impl PreferenceReader {
24 pub const fn new(
25 did: String,
26 db: deadpool_diesel::Pool<
27 deadpool_diesel::Manager<SqliteConnection>,
28 deadpool_diesel::sqlite::Object,
29 >,
30 ) -> Self {
31 Self { did, db }
32 }
33
34 pub async fn get_preferences(
35 &self,
36 namespace: Option<String>,
37 scope: AuthScope,
38 ) -> Result<Vec<RefPreferences>> {
39 use crate::schema::actor_store::account_pref::dsl as AccountPrefSchema;
40
41 let did = self.did.clone();
42 self.db
43 .get()
44 .await?
45 .interact(move |conn| {
46 let prefs_res = AccountPrefSchema::account_pref
47 .filter(AccountPrefSchema::did.eq(&did))
48 .select(AccountPref::as_select())
49 .order(AccountPrefSchema::id.asc())
50 .load(conn)?;
51 let account_prefs = prefs_res
52 .into_iter()
53 .filter(|pref| {
54 namespace
55 .as_ref()
56 .is_none_or(|namespace| pref_match_namespace(namespace, &pref.name))
57 })
58 .filter(|pref| pref_in_scope(scope.clone(), pref.name.clone()))
59 .map(|pref| {
60 let value_json_res = match pref.value_json {
61 None => bail!("preferences json null for {}", pref.name),
62 Some(value_json) => serde_json::from_str::<RefPreferences>(&value_json),
63 };
64 match value_json_res {
65 Err(error) => bail!(error.to_string()),
66 Ok(value_json) => Ok(value_json),
67 }
68 })
69 .collect::<Result<Vec<RefPreferences>>>()?;
70 Ok(account_prefs)
71 })
72 .await
73 .expect("Failed to get preferences")
74 }
75
76 #[tracing::instrument(skip_all)]
77 pub async fn put_preferences(
78 &self,
79 values: Vec<RefPreferences>,
80 namespace: String,
81 scope: AuthScope,
82 ) -> Result<()> {
83 let did = self.did.clone();
84 self.db.get().await?
85 .interact(move |conn| {
86 match values
87 .iter()
88 .all(|value| pref_match_namespace(&namespace, &value.get_type()))
89 {
90 false => bail!("Some preferences are not in the {namespace} namespace"),
91 true => {
92 if values
93 .iter().any(|value| !pref_in_scope(scope.clone(), value.get_type())) {
94 tracing::info!(
95 "@LOG: PreferenceReader::put_preferences() debug scope: {:?}, values: {:?}",
96 scope,
97 values
98 );
99 bail!("Do not have authorization to set preferences.");
100 }
101 // get all current prefs for user and prep new pref rows
102 use crate::schema::actor_store::account_pref::dsl as AccountPrefSchema;
103 let all_prefs = AccountPrefSchema::account_pref
104 .filter(AccountPrefSchema::did.eq(&did))
105 .select(AccountPref::as_select())
106 .load(conn)?;
107 let put_prefs = values
108 .into_iter()
109 .map(|value| {
110 Ok(AccountPref {
111 id: 0,
112 name: value.get_type(),
113 value_json: Some(serde_json::to_string(&value)?),
114 })
115 })
116 .collect::<Result<Vec<AccountPref>>>()?;
117
118 let all_pref_ids_in_namespace = all_prefs
119 .iter()
120 .filter(|pref| pref_match_namespace(&namespace, &pref.name))
121 .filter(|pref| pref_in_scope(scope.clone(), pref.name.clone()))
122 .map(|pref| pref.id)
123 .collect::<Vec<i32>>();
124 // replace all prefs in given namespace
125 if !all_pref_ids_in_namespace.is_empty() {
126 _ = delete(AccountPrefSchema::account_pref)
127 .filter(AccountPrefSchema::id.eq_any(all_pref_ids_in_namespace))
128 .execute(conn)?;
129 }
130 if !put_prefs.is_empty() {
131 _ = insert_into(AccountPrefSchema::account_pref)
132 .values(
133 put_prefs
134 .into_iter()
135 .map(|pref| {
136 (
137 AccountPrefSchema::did.eq(&did),
138 AccountPrefSchema::name.eq(pref.name),
139 AccountPrefSchema::valueJson.eq(pref.value_json),
140 )
141 })
142 .collect::<Vec<_>>(),
143 )
144 .execute(conn)?;
145 }
146 Ok(())
147 }
148 }
149 })
150 .await
151 .expect("Failed to put preferences")
152 }
153}