1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
//! Database connection facilities.
use activitystreams_vocabulary::field_access;
use sqlx::pool::{Pool, PoolOptions};
use sqlx::{Database, Postgres};
pub use activitystreams_vocabulary::DateTime;
pub use sqlx::types::Uuid;
use crate::crypto::{Salt, SymmetricKey};
use crate::{Error, Result, util};
/// Convenience alias for a database transaction.
pub type Transaction<'t> = sqlx::Transaction<'t, sqlx::postgres::Postgres>;
pub mod activity;
pub mod actor;
pub mod object;
mod alias;
mod config;
mod iri;
mod macros;
mod name;
mod table;
mod vocabulary;
pub use activity::*;
pub use actor::*;
pub use alias::*;
pub use config::DbConfig;
pub use iri::*;
pub use name::*;
pub use object::*;
pub use table::*;
pub use vocabulary::*;
/// Represents a database connection.
pub struct Db<DB: Database = Postgres> {
config: DbConfig,
pool: Option<Pool<DB>>,
}
impl<DB: Database> Db<DB> {
/// Creates a new [Db].
pub fn new() -> Self {
Self {
config: DbConfig::new(),
pool: None,
}
}
/// Gets a symmetric used for database cryptographic functions.
pub fn key(&self) -> Result<SymmetricKey> {
SymmetricKey::derive(self.config.password().as_bytes(), self.salt())
}
/// Gets a salt used for database cryptographic functions.
pub(crate) fn salt(&self) -> Salt {
// hash the config JSON serialization without the `password` field.
Salt::hash(self.config.to_string().as_bytes())
}
/// Gets a reference to the [Db] connection pool.
#[inline]
pub fn pool(&self) -> Result<&Pool<DB>> {
self.pool
.as_ref()
.ok_or(Error::sql("unset database connection"))
}
/// Sets the [Db] connection pool.
pub fn set_pool(&mut self, pool: Pool<DB>) {
self.pool = Some(pool);
}
/// Builder function that sets the [Db] connection pool.
pub fn with_pool(self, pool: Pool<DB>) -> Self {
Self {
pool: Some(pool),
..self
}
}
/// Creates a random V4 [Uuid].
pub fn rand_uuid(&self) -> Uuid {
util::rand_uuid()
}
}
impl Db<Postgres> {
/// Creates a database connection using the supplied configuration settings.
///
/// ```rust,no_run
/// use activityforge::{Result, db::{Db, DbConfig}};
///
/// # async fn test() -> Result<()> {
/// let username = "db_user";
/// let password = "db_password123";
/// let db_name = "test_db";
/// let host = "localhost";
/// let port = 5432u16;
///
/// let config = DbConfig::new()
/// .with_username(username)
/// .with_password(password)
/// .with_db_name(db_name)
/// .with_host(host)
/// .with_port(port);
///
/// let _db = Db::connect(config).await?;
/// # Ok(())
/// # }
/// ```
pub async fn connect(config: DbConfig) -> Result<Self> {
let conn_str = config.postgres_connection();
PoolOptions::<Postgres>::new()
.max_connections(config.max_connections())
.connect(conn_str.as_str())
.await
.map_err(Error::from)
.map(|pool| Self {
config,
pool: Some(pool),
})
}
/// Creates a connection for use in end-to-end integration tests.
#[cfg(feature = "e2e-tests")]
pub async fn test_connect(config: DbConfig) -> Result<Self> {
let conn_str = config.postgres_connection();
PoolOptions::<Postgres>::new()
.acquire_timeout(core::time::Duration::from_millis(1337))
.connect(conn_str.as_str())
.await
.map_err(Error::from)
.map(|pool| Self {
config,
pool: Some(pool),
})
}
}
field_access! {
Db {
/// [Db] configuration settings.
config: as_ref { DbConfig },
}
}
impl<DB: Database> Default for Db<DB> {
fn default() -> Self {
Self::new()
}
}