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