autopush_common/db/
mod.rs

1/// Contains the general Database access bits
2///
3/// Database access is abstracted into a DbClient impl
4/// which contains the required trait functions the
5/// application will need to perform in the database.
6/// Each of the abstractions contains a DbClientImpl
7/// that is responsible for carrying out the requested
8/// functions. Each of the data stores are VERY
9/// different, although the requested functions
10/// are fairly simple.
11use std::collections::{HashMap, HashSet};
12use std::result::Result as StdResult;
13
14use derive_builder::Builder;
15use serde::Serializer;
16use serde_derive::{Deserialize, Serialize};
17use uuid::Uuid;
18
19#[cfg(feature = "bigtable")]
20pub mod bigtable;
21pub mod client;
22pub mod error;
23pub mod models;
24pub mod reporter;
25pub mod routing;
26
27// used by integration testing
28pub mod mock;
29
30pub use reporter::spawn_pool_periodic_reporter;
31
32use crate::notification::Notification;
33use crate::util::timing::ms_since_epoch;
34use crate::MAX_ROUTER_TTL;
35use models::RangeKey;
36
37pub const USER_RECORD_VERSION: u64 = 1;
38
39#[derive(Eq, Debug, PartialEq)]
40pub enum StorageType {
41    INVALID,
42    #[cfg(feature = "bigtable")]
43    BigTable,
44}
45
46impl From<&str> for StorageType {
47    fn from(name: &str) -> Self {
48        match name.to_lowercase().as_str() {
49            #[cfg(feature = "bigtable")]
50            "bigtable" => Self::BigTable,
51            _ => Self::INVALID,
52        }
53    }
54}
55
56/// The type of storage to use.
57#[allow(clippy::vec_init_then_push)] // Because we are only pushing on feature flags.
58impl StorageType {
59    fn available<'a>() -> Vec<&'a str> {
60        #[allow(unused_mut)]
61        let mut result: Vec<&str> = Vec::new();
62        #[cfg(feature = "bigtable")]
63        result.push("Bigtable");
64        result
65    }
66
67    pub fn from_dsn(dsn: &Option<String>) -> Self {
68        debug!("Supported data types: {:?}", StorageType::available());
69        debug!("Checking DSN: {:?}", &dsn);
70        if dsn.is_none() {
71            let default = Self::available()[0];
72            info!("No DSN specified, failing over to old default dsn: {default}");
73            return Self::from(default);
74        }
75        let dsn = dsn.clone().unwrap_or_default();
76        #[cfg(feature = "bigtable")]
77        if dsn.starts_with("grpc") {
78            trace!("Found grpc");
79            // Credentials can be stored in either a path provided in an environment
80            // variable, or $HOME/.config/gcloud/applicaion_default_credentals.json
81            //
82            // NOTE: if no credentials are found, application will panic
83            //
84            if let Ok(cred) = std::env::var("GOOGLE_APPLICATION_CREDENTIALS") {
85                trace!("Env: {:?}", cred);
86            }
87            return Self::BigTable;
88        }
89        Self::INVALID
90    }
91}
92
93/// The universal settings for the database
94/// abstractor.
95#[derive(Clone, Debug, Default, Deserialize)]
96pub struct DbSettings {
97    /// Database connector string
98    pub dsn: Option<String>,
99    /// A JSON formatted dictionary containing Database settings that
100    /// are specific to the type of Data storage specified in the `dsn`
101    /// See the respective settings structure for
102    /// [crate::db::bigtable::BigTableDbSettings]
103    pub db_settings: String,
104}
105//TODO: add `From<autopush::settings::Settings> for DbSettings`?
106//TODO: add `From<autoendpoint::settings::Settings> for DbSettings`?
107
108/// Custom Uuid serializer
109///
110/// Serializes a Uuid as a simple string instead of hyphenated
111pub fn uuid_serializer<S>(x: &Uuid, s: S) -> StdResult<S::Ok, S::Error>
112where
113    S: Serializer,
114{
115    s.serialize_str(&x.simple().to_string())
116}
117
118#[derive(Clone, Default, Debug)]
119pub struct CheckStorageResponse {
120    /// The messages include a "topic"
121    /// "topics" are messages that replace prior messages of that topic.
122    /// (e.g. you can only have one message for a topic of "foo")
123    pub include_topic: bool,
124    /// The list of pending messages.
125    pub messages: Vec<Notification>,
126    /// All the messages up to this timestamp
127    pub timestamp: Option<u64>,
128}
129
130/// A user data record.
131#[derive(Deserialize, PartialEq, Debug, Clone, Serialize, Builder)]
132#[builder(default, setter(strip_option))]
133pub struct User {
134    /// The UAID. This is generally a UUID4. It needs to be globally
135    /// unique.
136    #[serde(serialize_with = "uuid_serializer")]
137    pub uaid: Uuid,
138    /// Time in milliseconds that the user last connected at
139    pub connected_at: u64,
140    /// Router type of the user
141    pub router_type: String,
142    /// Router-specific data
143    pub router_data: Option<HashMap<String, serde_json::Value>>,
144    /// Last node/port the client was or may be connected to
145    #[serde(skip_serializing_if = "Option::is_none")]
146    pub node_id: Option<String>,
147    /// Record version
148    #[serde(skip_serializing_if = "Option::is_none")]
149    pub record_version: Option<u64>,
150    /// the timestamp of the last notification sent to the user
151    /// This field is exclusive to the Bigtable data scheme
152    //TODO: rename this to `last_notification_timestamp`
153    #[serde(skip_serializing_if = "Option::is_none")]
154    pub current_timestamp: Option<u64>,
155    /// UUID4 version number for optimistic locking of updates on Bigtable
156    #[serde(skip_serializing)]
157    pub version: Option<Uuid>,
158    /// Set of user's channel ids. These are stored in router (user) record's
159    /// row in Bigtable. They are read along with the rest of the user record
160    /// so that them, along with every other field in the router record, will
161    /// automatically have their TTL (cell timestamp) reset during
162    /// [DbClient::update_user].
163    ///
164    /// This is solely used for the sake of that update thus private.
165    /// [DbClient::get_channels] is preferred for reading the latest version of
166    /// the channel ids (partly due to historical purposes but also is a more
167    /// flexible API that might benefit different, non Bigtable [DbClient]
168    /// backends that don't necessarily store the channel ids in the router
169    /// record).
170    priv_channels: HashSet<Uuid>,
171}
172
173impl Default for User {
174    fn default() -> Self {
175        let uaid = Uuid::new_v4();
176        //trace!(">>> Setting default uaid: {:?}", &uaid);
177        Self {
178            uaid,
179            connected_at: ms_since_epoch(),
180            router_type: "webpush".to_string(),
181            router_data: None,
182            node_id: None,
183            record_version: Some(USER_RECORD_VERSION),
184            current_timestamp: None,
185            version: Some(Uuid::new_v4()),
186            priv_channels: HashSet::new(),
187        }
188    }
189}
190
191impl User {
192    /// Return a new [UserBuilder] (generated from [derive_builder::Builder])
193    pub fn builder() -> UserBuilder {
194        UserBuilder::default()
195    }
196
197    pub fn channel_count(&self) -> usize {
198        self.priv_channels.len()
199    }
200}
201
202#[cfg(test)]
203mod tests {
204    use super::{User, USER_RECORD_VERSION};
205
206    #[test]
207    fn user_defaults() {
208        let user = User::builder().current_timestamp(22).build().unwrap();
209        assert_eq!(user.current_timestamp, Some(22));
210        assert_eq!(user.router_type, "webpush".to_owned());
211        assert_eq!(user.record_version, Some(USER_RECORD_VERSION));
212    }
213}