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 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}