Skip to main content

scalar_cms/
db.rs

1use std::fmt::Debug;
2use std::marker::PhantomData;
3use std::{error::Error, sync::Arc};
4
5use chrono::{DateTime, Utc};
6use scalar_expr::Expression;
7use serde::{Deserialize, Serialize};
8use thiserror::Error;
9
10use crate::{validations::Valid, Document, Item};
11
12#[derive(Error, Debug)]
13pub enum AuthenticationError<DE: Error> {
14    #[error("Invalid token provided")]
15    BadToken,
16    #[error("Invalid credentials provided")]
17    BadCredentials,
18    #[error("Database error: {0}")]
19    DatabaseError(#[from] DE),
20}
21
22#[derive(Serialize, Deserialize)]
23pub struct Credentials {
24    email: String,
25    password: String,
26}
27
28impl Credentials {
29    #[must_use]
30    pub fn email(&self) -> &str {
31        &self.email
32    }
33
34    #[must_use]
35    pub fn password(&self) -> &str {
36        &self.password
37    }
38}
39
40impl Debug for Credentials {
41    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
42        f.debug_struct("Credentials")
43            .field("email", &self.email)
44            .field("password", &"<REDACTED>")
45            .finish()
46    }
47}
48
49#[derive(Serialize, Deserialize, Debug, Clone)]
50pub struct User {
51    email: Arc<str>,
52    name: Arc<str>,
53    profile_picture_url: Arc<str>,
54    admin: bool,
55}
56
57impl User {
58    pub fn new(
59        email: impl Into<String>,
60        name: impl Into<String>,
61        profile_picture_url: impl Into<String>,
62        admin: bool,
63    ) -> Self {
64        Self {
65            email: email.into().into(),
66            name: name.into().into(),
67            profile_picture_url: profile_picture_url.into().into(),
68            admin,
69        }
70    }
71
72    #[must_use]
73    pub fn name(&self) -> &str {
74        &self.name
75    }
76
77    #[must_use]
78    pub fn profile_picture_url(&self) -> &str {
79        &self.profile_picture_url
80    }
81
82    #[must_use]
83    pub fn admin(&self) -> bool {
84        self.admin
85    }
86}
87
88#[trait_variant::make(Send + Sized)]
89pub trait DatabaseFactory {
90    type Error: Error;
91    type Connection: DatabaseConnection + Sync;
92
93    async fn init(&self) -> Result<Self::Connection, Self::Error>;
94    async fn init_system(&self) -> Result<Self::Connection, Self::Error>;
95}
96
97pub struct ValidationContext<'a, DB: DatabaseConnection, D: Document> {
98    conn: &'a DB,
99    excluded_id: &'a str,
100    field_name: &'a str,
101    phantom: PhantomData<D>,
102}
103
104impl<DB: DatabaseConnection, D: Document> Clone for ValidationContext<'_, DB, D> {
105    fn clone(&self) -> Self {
106        *self
107    }
108}
109impl<DB: DatabaseConnection, D: Document> Copy for ValidationContext<'_, DB, D> {}
110
111impl<'a, 'b, DB: DatabaseConnection + ContentActions<D>, D: Document> ValidationContext<'a, DB, D>
112where
113    'b: 'a,
114    'a: 'b,
115{
116    pub fn new(conn: &'a DB, excluded_id: &'a str) -> Self {
117        ValidationContext {
118            conn,
119            excluded_id,
120            field_name: "",
121            phantom: PhantomData,
122        }
123    }
124
125    #[must_use]
126    pub fn for_field(&self, field_name: &'b str) -> ValidationContext<'b, DB, D> {
127        Self {
128            conn: self.conn,
129            excluded_id: self.excluded_id,
130            field_name,
131            phantom: PhantomData,
132        }
133    }
134
135    pub async fn all(&self, expr: Expression) -> Result<bool, DB::Error> {
136        self.conn
137            .vctx_all(self.excluded_id, self.field_name, expr)
138            .await
139    }
140    pub async fn none(&self, expr: Expression) -> Result<bool, DB::Error> {
141        self.conn
142            .vctx_none(self.excluded_id, self.field_name, expr)
143            .await
144    }
145    pub async fn any(&self, expr: Expression) -> Result<bool, DB::Error> {
146        self.conn
147            .vctx_any(self.excluded_id, self.field_name, expr)
148            .await
149    }
150}
151
152#[derive(Debug)]
153pub struct Authenticated<DB: DatabaseConnection> {
154    conn: DB,
155    user: User,
156}
157
158impl<DB: DatabaseConnection> Authenticated<DB> {
159    /// Creates an authenticated DB connection.
160    ///
161    /// # Errors
162    ///
163    /// This function will return an error if authentication fails.
164    pub async fn authenticate(
165        conn: DB,
166        token: &str,
167    ) -> Result<Self, AuthenticationError<DB::Error>> {
168        Ok(Self {
169            user: conn.authenticate(token).await?,
170            conn,
171        })
172    }
173
174    pub fn me(&self) -> User {
175        self.user.clone()
176    }
177
178    pub fn inner(&self) -> &DB {
179        &self.conn
180    }
181}
182
183#[trait_variant::make(Send + Sized)]
184pub trait DatabaseConnection {
185    type Error: Error;
186
187    async fn authenticate(&self, jwt: &str) -> Result<User, AuthenticationError<Self::Error>>;
188    async fn signin(
189        &self,
190        credentials: Credentials,
191    ) -> Result<String, AuthenticationError<Self::Error>>;
192    #[cfg(feature = "oidc")]
193    async fn signin_oidc<
194        AC: openidconnect::AdditionalClaims + Send + Sync,
195        GC: openidconnect::GenderClaim + Send + Sync,
196    >(
197        &self,
198        user_info: &openidconnect::IdTokenClaims<AC, GC>,
199    ) -> Result<String, AuthenticationError<Self::Error>>;
200}
201
202#[trait_variant::make(Send + Sized)]
203pub trait ContentActions<D: Document>: DatabaseConnection {
204    async fn draft(
205        conn: &Authenticated<Self>,
206        id: &str,
207        data: serde_json::Value,
208    ) -> Result<Item<serde_json::Value>, Self::Error>;
209    async fn delete_draft(
210        conn: &Authenticated<Self>,
211        id: &str,
212    ) -> Result<Item<serde_json::Value>, Self::Error>;
213
214    async fn publish(
215        conn: &Authenticated<Self>,
216        id: &str,
217        publish_at: Option<DateTime<Utc>>,
218        data: Valid<D>,
219    ) -> Result<Item<D>, Self::Error>;
220    async fn unpublish(conn: &Authenticated<Self>, id: &str) -> Result<Option<D>, Self::Error>;
221
222    async fn put(conn: &Authenticated<Self>, item: Item<D>) -> Result<Item<D>, Self::Error>;
223    async fn delete(
224        conn: &Authenticated<Self>,
225        id: &str,
226    ) -> Result<Option<Item<serde_json::Value>>, Self::Error>;
227    async fn get_all(&self) -> Result<Vec<Item<serde_json::Value>>, Self::Error>;
228    async fn get_by_id(&self, id: &str) -> Result<Option<Item<serde_json::Value>>, Self::Error>;
229
230    async fn vctx_all(
231        &self,
232        excl_id: &str,
233        field_name: &str,
234        expression: Expression,
235    ) -> Result<bool, Self::Error>;
236    async fn vctx_none(
237        &self,
238        excl_id: &str,
239        field_name: &str,
240        expression: Expression,
241    ) -> Result<bool, Self::Error>;
242    async fn vctx_any(
243        &self,
244        excl_id: &str,
245        field_name: &str,
246        expression: Expression,
247    ) -> Result<bool, Self::Error>;
248}