1use std::future::Future;
2
3use chrono::{DateTime, Utc};
4
5use super::{Db, DbPool, DbRow, placeholder};
6use super::{DeleteBuilder, UpdateBuilder};
7use crate::error::AppResult;
8
9#[derive(Debug, Clone)]
17pub enum WhereValue {
18 Int(i64),
19 Float(f64),
20 String(String),
21 Bool(bool),
22 DateTime(DateTime<Utc>),
23}
24
25impl From<i64> for WhereValue {
26 fn from(v: i64) -> Self { WhereValue::Int(v) }
27}
28
29impl From<i32> for WhereValue {
30 fn from(v: i32) -> Self { WhereValue::Int(v as i64) }
31}
32
33impl From<f64> for WhereValue {
34 fn from(v: f64) -> Self { WhereValue::Float(v) }
35}
36
37impl From<f32> for WhereValue {
38 fn from(v: f32) -> Self { WhereValue::Float(v as f64) }
39}
40
41impl From<&str> for WhereValue {
42 fn from(v: &str) -> Self { WhereValue::String(v.to_string()) }
43}
44
45impl From<String> for WhereValue {
46 fn from(v: String) -> Self { WhereValue::String(v) }
47}
48
49impl From<bool> for WhereValue {
50 fn from(v: bool) -> Self { WhereValue::Bool(v) }
51}
52
53impl From<DateTime<Utc>> for WhereValue {
54 fn from(v: DateTime<Utc>) -> Self { WhereValue::DateTime(v) }
55}
56
57pub trait CrudRepository: Sized + Send + Unpin + for<'r> sqlx::FromRow<'r, DbRow> {
75 const TABLE: &'static str;
76 const ENTITY_NAME: &'static str;
77 const HAS_SLUG: bool = false;
78
79 const SOFT_DELETE: bool = false;
82
83 fn soft_filter() -> &'static str {
87 if Self::SOFT_DELETE { " AND deleted_at IS NULL" } else { "" }
88 }
89
90 fn soft_where() -> &'static str {
91 if Self::SOFT_DELETE { " WHERE deleted_at IS NULL" } else { "" }
92 }
93
94 fn find_by_id(
97 pool: &DbPool,
98 id: i64,
99 ) -> impl Future<Output = AppResult<Option<Self>>> + Send {
100 async move {
101 let query = format!(
102 "SELECT * FROM {} WHERE id = {}{}",
103 Self::TABLE, placeholder(1), Self::soft_filter()
104 );
105 let result = sqlx::query_as::<Db, Self>(&query)
106 .bind(id)
107 .fetch_optional(pool)
108 .await?;
109 Ok(result)
110 }
111 }
112
113 fn find_by_slug(
116 pool: &DbPool,
117 slug: &str,
118 ) -> impl Future<Output = AppResult<Option<Self>>> + Send {
119 async move {
120 if !Self::HAS_SLUG {
121 return Err(crate::error::AppError::Internal(
122 format!("find_by_slug called on {} which does not have HAS_SLUG = true", Self::TABLE)
123 ));
124 }
125 let query = format!(
126 "SELECT * FROM {} WHERE slug = {}{}",
127 Self::TABLE, placeholder(1), Self::soft_filter()
128 );
129 let result = sqlx::query_as::<Db, Self>(&query)
130 .bind(slug)
131 .fetch_optional(pool)
132 .await?;
133 Ok(result)
134 }
135 }
136
137 fn count(pool: &DbPool) -> impl Future<Output = AppResult<i64>> + Send {
140 async move {
141 let query = format!("SELECT COUNT(*) FROM {}{}", Self::TABLE, Self::soft_where());
142 let (count,): (i64,) = sqlx::query_as(&query).fetch_one(pool).await?;
143 Ok(count)
144 }
145 }
146
147 fn exists(pool: &DbPool, id: i64) -> impl Future<Output = AppResult<bool>> + Send {
148 async move {
149 let query = format!(
150 "SELECT COUNT(*) FROM {} WHERE id = {}{}",
151 Self::TABLE, placeholder(1), Self::soft_filter()
152 );
153 let (count,): (i64,) = sqlx::query_as(&query).bind(id).fetch_one(pool).await?;
154 Ok(count > 0)
155 }
156 }
157
158 fn delete(pool: &DbPool, id: i64) -> impl Future<Output = AppResult<u64>> + Send {
162 async move {
163 if Self::SOFT_DELETE {
164 UpdateBuilder::table(Self::TABLE)
165 .set_raw("deleted_at", "NOW()")
166 .where_eq("id", id)
167 .execute(pool)
168 .await
169 } else {
170 DeleteBuilder::from(Self::TABLE)
171 .where_eq("id", id)
172 .execute(pool)
173 .await
174 }
175 }
176 }
177
178 fn force_delete(pool: &DbPool, id: i64) -> impl Future<Output = AppResult<u64>> + Send {
180 async move {
181 DeleteBuilder::from(Self::TABLE)
182 .where_eq("id", id)
183 .execute(pool)
184 .await
185 }
186 }
187
188 fn restore(pool: &DbPool, id: i64) -> impl Future<Output = AppResult<u64>> + Send {
190 async move {
191 if !Self::SOFT_DELETE {
192 return Err(crate::error::AppError::Internal(
193 format!("restore called on {} which does not have SOFT_DELETE = true", Self::TABLE)
194 ));
195 }
196 UpdateBuilder::table(Self::TABLE)
197 .set_raw("deleted_at", "NULL")
198 .where_eq("id", id)
199 .execute(pool)
200 .await
201 }
202 }
203
204 fn find_all(
211 pool: &DbPool,
212 order: Option<(&str, &str)>,
213 ) -> impl Future<Output = AppResult<Vec<Self>>> + Send {
214 let mut query = format!("SELECT * FROM {}{}", Self::TABLE, Self::soft_where());
215 if let Some((col, dir)) = order {
216 query.push_str(&format!(" ORDER BY {} {}", col, dir));
217 }
218 async move {
219 let items = sqlx::query_as::<Db, Self>(&query).fetch_all(pool).await?;
220 Ok(items)
221 }
222 }
223
224 fn find_all_with_trashed(
226 pool: &DbPool,
227 order: Option<(&str, &str)>,
228 ) -> impl Future<Output = AppResult<Vec<Self>>> + Send {
229 let mut query = format!("SELECT * FROM {}", Self::TABLE);
230 if let Some((col, dir)) = order {
231 query.push_str(&format!(" ORDER BY {} {}", col, dir));
232 }
233 async move {
234 let items = sqlx::query_as::<Db, Self>(&query).fetch_all(pool).await?;
235 Ok(items)
236 }
237 }
238
239 fn find_where(
245 pool: &DbPool,
246 conditions: &[(&str, WhereValue)],
247 ) -> impl Future<Output = AppResult<Option<Self>>> + Send {
248 let (sql, values) = build_where_query(Self::TABLE, conditions, None, Self::SOFT_DELETE);
249 async move {
250 let mut query = sqlx::query_as::<Db, Self>(&sql);
251 for value in &values {
252 query = bind_where_value_as(query, value);
253 }
254 Ok(query.fetch_optional(pool).await?)
255 }
256 }
257
258 fn find_all_where(
262 pool: &DbPool,
263 conditions: &[(&str, WhereValue)],
264 order: Option<(&str, &str)>,
265 ) -> impl Future<Output = AppResult<Vec<Self>>> + Send {
266 let (sql, values) = build_where_query(Self::TABLE, conditions, order, Self::SOFT_DELETE);
267 async move {
268 let mut query = sqlx::query_as::<Db, Self>(&sql);
269 for value in &values {
270 query = bind_where_value_as(query, value);
271 }
272 Ok(query.fetch_all(pool).await?)
273 }
274 }
275}
276
277fn build_where_query(table: &str, conditions: &[(&str, WhereValue)], order: Option<(&str, &str)>, soft_delete: bool) -> (String, Vec<WhereValue>) {
278 let mut clauses: Vec<String> = conditions.iter().enumerate()
279 .map(|(i, (col, _))| format!("{} = {}", col, placeholder(i + 1)))
280 .collect();
281 if soft_delete {
282 clauses.push("deleted_at IS NULL".to_string());
283 }
284 let values: Vec<WhereValue> = conditions.iter().map(|(_, v)| v.clone()).collect();
285 let mut sql = if clauses.is_empty() {
286 format!("SELECT * FROM {}", table)
287 } else {
288 format!("SELECT * FROM {} WHERE {}", table, clauses.join(" AND "))
289 };
290 if let Some((col, dir)) = order {
291 sql.push_str(&format!(" ORDER BY {} {}", col, dir));
292 }
293 (sql, values)
294}
295
296fn bind_where_value_as<'q, T>(
297 query: sqlx::query::QueryAs<'q, Db, T, <Db as sqlx::Database>::Arguments<'q>>,
298 value: &'q WhereValue,
299) -> sqlx::query::QueryAs<'q, Db, T, <Db as sqlx::Database>::Arguments<'q>>
300where
301 T: Send + Unpin + for<'r> sqlx::FromRow<'r, DbRow>,
302{
303 match value {
304 WhereValue::Int(v) => query.bind(*v),
305 WhereValue::Float(v) => query.bind(*v),
306 WhereValue::String(v) => query.bind(v.as_str()),
307 WhereValue::Bool(v) => query.bind(*v),
308 WhereValue::DateTime(v) => query.bind(*v),
309 }
310}