forked from
tranquil.farm/tranquil-pds
Our Personal Data Server from scratch!
11 kB
393 lines
1use async_trait::async_trait;
2use chrono::{DateTime, Utc};
3use serde::{Deserialize, Serialize};
4use tranquil_types::{CidLink, Did, Handle};
5use uuid::Uuid;
6
7use crate::DbError;
8use crate::invite_code::{InviteCodeError, ValidatedInviteCode};
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
11pub enum InviteCodeSortOrder {
12 #[default]
13 Recent,
14 Usage,
15}
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
18pub enum InviteCodeState {
19 #[default]
20 Active,
21 Disabled,
22}
23
24impl InviteCodeState {
25 pub fn is_active(self) -> bool {
26 matches!(self, Self::Active)
27 }
28
29 pub fn is_disabled(self) -> bool {
30 matches!(self, Self::Disabled)
31 }
32}
33
34impl From<bool> for InviteCodeState {
35 fn from(disabled: bool) -> Self {
36 if disabled {
37 Self::Disabled
38 } else {
39 Self::Active
40 }
41 }
42}
43
44impl From<Option<bool>> for InviteCodeState {
45 fn from(disabled: Option<bool>) -> Self {
46 Self::from(disabled.unwrap_or(false))
47 }
48}
49
50impl From<InviteCodeState> for bool {
51 fn from(state: InviteCodeState) -> Self {
52 matches!(state, InviteCodeState::Disabled)
53 }
54}
55
56#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, sqlx::Type)]
57#[sqlx(type_name = "comms_channel", rename_all = "snake_case")]
58pub enum CommsChannel {
59 Email,
60 Discord,
61 Telegram,
62 Signal,
63}
64
65#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, sqlx::Type)]
66#[sqlx(type_name = "comms_type", rename_all = "snake_case")]
67pub enum CommsType {
68 Welcome,
69 EmailVerification,
70 PasswordReset,
71 EmailUpdate,
72 AccountDeletion,
73 AdminEmail,
74 PlcOperation,
75 TwoFactorCode,
76 PasskeyRecovery,
77 LegacyLoginAlert,
78 MigrationVerification,
79 ChannelVerification,
80}
81
82#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, sqlx::Type)]
83#[sqlx(type_name = "comms_status", rename_all = "snake_case")]
84pub enum CommsStatus {
85 Pending,
86 Processing,
87 Sent,
88 Failed,
89}
90
91#[derive(Debug, Clone, Serialize, Deserialize)]
92pub struct QueuedComms {
93 pub id: Uuid,
94 pub user_id: Option<Uuid>,
95 pub channel: CommsChannel,
96 pub comms_type: CommsType,
97 pub status: CommsStatus,
98 pub recipient: String,
99 pub subject: Option<String>,
100 pub body: String,
101 pub metadata: Option<serde_json::Value>,
102 pub attempts: i32,
103 pub max_attempts: i32,
104 pub last_error: Option<String>,
105 pub created_at: DateTime<Utc>,
106 pub updated_at: DateTime<Utc>,
107 pub scheduled_for: DateTime<Utc>,
108 pub processed_at: Option<DateTime<Utc>>,
109}
110
111#[derive(Debug, Clone, Serialize, Deserialize)]
112pub struct InviteCodeInfo {
113 pub code: String,
114 pub available_uses: i32,
115 pub state: InviteCodeState,
116 pub for_account: Option<Did>,
117 pub created_at: DateTime<Utc>,
118 pub created_by: Option<Did>,
119}
120
121#[derive(Debug, Clone, Serialize, Deserialize)]
122pub struct InviteCodeUse {
123 pub code: String,
124 pub used_by_did: Did,
125 pub used_by_handle: Option<Handle>,
126 pub used_at: DateTime<Utc>,
127}
128
129#[derive(Debug, Clone, Serialize, Deserialize)]
130pub struct InviteCodeRow {
131 pub code: String,
132 pub available_uses: i32,
133 pub disabled: Option<bool>,
134 pub created_by_user: Uuid,
135 pub created_at: DateTime<Utc>,
136}
137
138impl InviteCodeRow {
139 pub fn state(&self) -> InviteCodeState {
140 InviteCodeState::from(self.disabled)
141 }
142}
143
144#[derive(Debug, Clone)]
145pub struct ReservedSigningKey {
146 pub id: Uuid,
147 pub private_key_bytes: Vec<u8>,
148}
149
150#[derive(Debug, Clone)]
151pub struct DeletionRequest {
152 pub did: Did,
153 pub expires_at: DateTime<Utc>,
154}
155
156#[async_trait]
157pub trait InfraRepository: Send + Sync {
158 #[allow(clippy::too_many_arguments)]
159 async fn enqueue_comms(
160 &self,
161 user_id: Option<Uuid>,
162 channel: CommsChannel,
163 comms_type: CommsType,
164 recipient: &str,
165 subject: Option<&str>,
166 body: &str,
167 metadata: Option<serde_json::Value>,
168 ) -> Result<Uuid, DbError>;
169
170 async fn fetch_pending_comms(
171 &self,
172 now: DateTime<Utc>,
173 batch_size: i64,
174 ) -> Result<Vec<QueuedComms>, DbError>;
175
176 async fn mark_comms_sent(&self, id: Uuid) -> Result<(), DbError>;
177
178 async fn mark_comms_failed(&self, id: Uuid, error: &str) -> Result<(), DbError>;
179
180 async fn create_invite_code(
181 &self,
182 code: &str,
183 use_count: i32,
184 for_account: Option<&Did>,
185 ) -> Result<bool, DbError>;
186
187 async fn create_invite_codes_batch(
188 &self,
189 codes: &[String],
190 use_count: i32,
191 created_by_user: Uuid,
192 for_account: Option<&Did>,
193 ) -> Result<(), DbError>;
194
195 async fn get_invite_code_available_uses(&self, code: &str) -> Result<Option<i32>, DbError>;
196
197 async fn validate_invite_code<'a>(
198 &self,
199 code: &'a str,
200 ) -> Result<ValidatedInviteCode<'a>, InviteCodeError>;
201
202 async fn decrement_invite_code_uses(
203 &self,
204 code: &ValidatedInviteCode<'_>,
205 ) -> Result<(), DbError>;
206
207 async fn record_invite_code_use(
208 &self,
209 code: &ValidatedInviteCode<'_>,
210 used_by_user: Uuid,
211 ) -> Result<(), DbError>;
212
213 async fn get_invite_codes_for_account(
214 &self,
215 for_account: &Did,
216 ) -> Result<Vec<InviteCodeInfo>, DbError>;
217
218 async fn get_invite_code_uses(&self, code: &str) -> Result<Vec<InviteCodeUse>, DbError>;
219
220 async fn disable_invite_codes_by_code(&self, codes: &[String]) -> Result<(), DbError>;
221
222 async fn disable_invite_codes_by_account(&self, accounts: &[Did]) -> Result<(), DbError>;
223
224 async fn list_invite_codes(
225 &self,
226 cursor: Option<&str>,
227 limit: i64,
228 sort: InviteCodeSortOrder,
229 ) -> Result<Vec<InviteCodeRow>, DbError>;
230
231 async fn get_user_dids_by_ids(&self, user_ids: &[Uuid]) -> Result<Vec<(Uuid, Did)>, DbError>;
232
233 async fn get_invite_code_uses_batch(
234 &self,
235 codes: &[String],
236 ) -> Result<Vec<InviteCodeUse>, DbError>;
237
238 async fn get_invites_created_by_user(
239 &self,
240 user_id: Uuid,
241 ) -> Result<Vec<InviteCodeInfo>, DbError>;
242
243 async fn get_invite_code_info(&self, code: &str) -> Result<Option<InviteCodeInfo>, DbError>;
244
245 async fn get_invite_codes_by_users(
246 &self,
247 user_ids: &[Uuid],
248 ) -> Result<Vec<(Uuid, InviteCodeInfo)>, DbError>;
249
250 async fn get_invite_code_used_by_user(&self, user_id: Uuid) -> Result<Option<String>, DbError>;
251
252 async fn delete_invite_code_uses_by_user(&self, user_id: Uuid) -> Result<(), DbError>;
253
254 async fn delete_invite_codes_by_user(&self, user_id: Uuid) -> Result<(), DbError>;
255
256 async fn reserve_signing_key(
257 &self,
258 did: Option<&Did>,
259 public_key_did_key: &str,
260 private_key_bytes: &[u8],
261 expires_at: DateTime<Utc>,
262 ) -> Result<Uuid, DbError>;
263
264 async fn get_reserved_signing_key(
265 &self,
266 public_key_did_key: &str,
267 ) -> Result<Option<ReservedSigningKey>, DbError>;
268
269 async fn mark_signing_key_used(&self, key_id: Uuid) -> Result<(), DbError>;
270
271 async fn create_deletion_request(
272 &self,
273 token: &str,
274 did: &Did,
275 expires_at: DateTime<Utc>,
276 ) -> Result<(), DbError>;
277
278 async fn get_deletion_request(&self, token: &str) -> Result<Option<DeletionRequest>, DbError>;
279
280 async fn delete_deletion_request(&self, token: &str) -> Result<(), DbError>;
281
282 async fn delete_deletion_requests_by_did(&self, did: &Did) -> Result<(), DbError>;
283
284 async fn upsert_account_preference(
285 &self,
286 user_id: Uuid,
287 name: &str,
288 value_json: serde_json::Value,
289 ) -> Result<(), DbError>;
290
291 async fn insert_account_preference_if_not_exists(
292 &self,
293 user_id: Uuid,
294 name: &str,
295 value_json: serde_json::Value,
296 ) -> Result<(), DbError>;
297
298 async fn get_server_config(&self, key: &str) -> Result<Option<String>, DbError>;
299
300 async fn health_check(&self) -> Result<bool, DbError>;
301
302 async fn insert_report(
303 &self,
304 id: i64,
305 reason_type: &str,
306 reason: Option<&str>,
307 subject_json: serde_json::Value,
308 reported_by_did: &Did,
309 created_at: DateTime<Utc>,
310 ) -> Result<(), DbError>;
311
312 async fn delete_plc_tokens_for_user(&self, user_id: Uuid) -> Result<(), DbError>;
313
314 async fn insert_plc_token(
315 &self,
316 user_id: Uuid,
317 token: &str,
318 expires_at: DateTime<Utc>,
319 ) -> Result<(), DbError>;
320
321 async fn get_plc_token_expiry(
322 &self,
323 user_id: Uuid,
324 token: &str,
325 ) -> Result<Option<DateTime<Utc>>, DbError>;
326
327 async fn delete_plc_token(&self, user_id: Uuid, token: &str) -> Result<(), DbError>;
328
329 async fn get_account_preferences(
330 &self,
331 user_id: Uuid,
332 ) -> Result<Vec<(String, serde_json::Value)>, DbError>;
333
334 async fn replace_namespace_preferences(
335 &self,
336 user_id: Uuid,
337 namespace: &str,
338 preferences: Vec<(String, serde_json::Value)>,
339 ) -> Result<(), DbError>;
340
341 async fn get_notification_history(
342 &self,
343 user_id: Uuid,
344 limit: i64,
345 ) -> Result<Vec<NotificationHistoryRow>, DbError>;
346
347 async fn get_server_configs(&self, keys: &[&str]) -> Result<Vec<(String, String)>, DbError>;
348
349 async fn upsert_server_config(&self, key: &str, value: &str) -> Result<(), DbError>;
350
351 async fn delete_server_config(&self, key: &str) -> Result<(), DbError>;
352
353 async fn get_blob_storage_key_by_cid(&self, cid: &CidLink) -> Result<Option<String>, DbError>;
354
355 async fn delete_blob_by_cid(&self, cid: &CidLink) -> Result<(), DbError>;
356
357 async fn get_admin_account_info_by_did(
358 &self,
359 did: &Did,
360 ) -> Result<Option<AdminAccountInfo>, DbError>;
361
362 async fn get_admin_account_infos_by_dids(
363 &self,
364 dids: &[Did],
365 ) -> Result<Vec<AdminAccountInfo>, DbError>;
366
367 async fn get_invite_code_uses_by_users(
368 &self,
369 user_ids: &[Uuid],
370 ) -> Result<Vec<(Uuid, String)>, DbError>;
371}
372
373#[derive(Debug, Clone)]
374pub struct NotificationHistoryRow {
375 pub created_at: DateTime<Utc>,
376 pub channel: CommsChannel,
377 pub comms_type: CommsType,
378 pub status: CommsStatus,
379 pub subject: Option<String>,
380 pub body: String,
381}
382
383#[derive(Debug, Clone)]
384pub struct AdminAccountInfo {
385 pub id: Uuid,
386 pub did: Did,
387 pub handle: Handle,
388 pub email: Option<String>,
389 pub created_at: DateTime<Utc>,
390 pub invites_disabled: bool,
391 pub email_verified: bool,
392 pub deactivated_at: Option<DateTime<Utc>>,
393}