1use super::{Error, JsonSnafu, SchemaOption, SchemaView};
16use chrono::DateTime;
17use serde::{Deserialize, Serialize};
18use serde_json::json;
19use snafu::ResultExt;
20use sqlx::{Pool, Postgres, QueryBuilder};
21use std::collections::HashMap;
22use std::future::Future;
23
24type Result<T> = std::result::Result<T, Error>;
25
26#[derive(Debug, Clone, Deserialize, Default)]
27pub struct ModelListParams {
28 pub page: u64,
29 pub limit: u64,
30 pub order_by: Option<String>,
31 pub keyword: Option<String>,
32 pub filters: Option<String>,
33}
34
35impl ModelListParams {
36 pub fn parse_filters(&self) -> Result<Option<HashMap<String, String>>> {
37 if let Some(filters) = &self.filters {
38 let filters: HashMap<String, String> =
39 serde_json::from_str(filters).context(JsonSnafu)?;
40 Ok(Some(filters))
41 } else {
42 Ok(None)
43 }
44 }
45
46 pub fn push_pagination(&self, qb: &mut QueryBuilder<'_, Postgres>) {
47 let order_by = self.order_by.as_deref().unwrap_or("id");
48 push_order_by(qb, order_by);
49 let limit = self.limit.min(200);
50 let offset = (self.page.max(1) - 1) * limit;
51 qb.push(format!(" LIMIT {limit} OFFSET {offset}"));
52 }
53}
54
55pub fn push_order_by(qb: &mut QueryBuilder<'_, Postgres>, order_by: &str) {
57 let (col, dir) = if let Some(col) = order_by.strip_prefix('-') {
58 (col, "DESC")
59 } else {
60 (order_by, "ASC")
61 };
62 if col.chars().all(|c| c.is_alphanumeric() || c == '_') {
63 qb.push(format!(" ORDER BY {col} {dir}"));
64 }
65}
66
67pub trait Model: Send + Sync {
68 type Output: Serialize + Send;
69 fn new() -> Self;
70 fn schema_view<'a>(
71 &'a self,
72 pool: &'a Pool<Postgres>,
73 ) -> impl Future<Output = SchemaView> + Send + 'a;
74 fn keyword(&self) -> String {
75 String::new()
76 }
77 fn push_filter_conditions<'args>(
78 &self,
79 _qb: &mut QueryBuilder<'args, Postgres>,
80 _filters: &HashMap<String, String>,
81 ) -> Result<()> {
82 Ok(())
83 }
84 fn push_conditions<'args>(
85 &self,
86 qb: &mut QueryBuilder<'args, Postgres>,
87 params: &ModelListParams,
88 ) -> Result<()> {
89 qb.push(" WHERE deleted_at IS NULL");
90
91 let col = self.keyword();
92 if !col.is_empty() && col.chars().all(|c| c.is_alphanumeric() || c == '_') {
93 if let Some(keyword) = ¶ms.keyword {
94 qb.push(format!(" AND {col} LIKE "));
95 qb.push_bind(format!("%{keyword}%"));
96 }
97 }
98
99 if let Some(filters) = params.parse_filters()? {
100 if let Some(modified) = filters.get("modified")
101 && let Some((start, end)) = modified.split_once(',')
102 {
103 if let Ok(dt) = DateTime::parse_from_rfc3339(start) {
104 qb.push(" AND modified >= ");
105 qb.push_bind(dt.naive_utc());
106 }
107 if let Ok(dt) = DateTime::parse_from_rfc3339(end) {
108 qb.push(" AND modified <= ");
109 qb.push_bind(dt.naive_utc());
110 }
111 }
112 self.push_filter_conditions(qb, &filters)?;
113 }
114
115 Ok(())
116 }
117 fn insert<'a>(
118 &'a self,
119 _pool: &'a Pool<Postgres>,
120 _params: serde_json::Value,
121 ) -> impl Future<Output = Result<u64>> + Send + 'a {
122 async {
123 Err(Error::NotSupported {
124 name: "insert".to_string(),
125 })
126 }
127 }
128 fn get_by_id<'a>(
129 &'a self,
130 _pool: &'a Pool<Postgres>,
131 _id: u64,
132 ) -> impl Future<Output = Result<Option<Self::Output>>> + Send + 'a {
133 async {
134 Err(Error::NotSupported {
135 name: "get_by_id".to_string(),
136 })
137 }
138 }
139 fn delete_by_id<'a>(
140 &'a self,
141 _pool: &'a Pool<Postgres>,
142 _id: u64,
143 ) -> impl Future<Output = Result<()>> + Send + 'a {
144 async {
145 Err(Error::NotSupported {
146 name: "delete_by_id".to_string(),
147 })
148 }
149 }
150 fn update_by_id<'a>(
151 &'a self,
152 _pool: &'a Pool<Postgres>,
153 _id: u64,
154 _params: serde_json::Value,
155 ) -> impl Future<Output = Result<()>> + Send + 'a {
156 async {
157 Err(Error::NotSupported {
158 name: "update_by_id".to_string(),
159 })
160 }
161 }
162 fn count<'a>(
163 &'a self,
164 _pool: &'a Pool<Postgres>,
165 _params: &'a ModelListParams,
166 ) -> impl Future<Output = Result<i64>> + Send + 'a {
167 async {
168 Err(Error::NotSupported {
169 name: "count".to_string(),
170 })
171 }
172 }
173 fn list<'a>(
174 &'a self,
175 _pool: &'a Pool<Postgres>,
176 _params: &'a ModelListParams,
177 ) -> impl Future<Output = Result<Vec<Self::Output>>> + Send + 'a {
178 async {
179 Err(Error::NotSupported {
180 name: "list".to_string(),
181 })
182 }
183 }
184 fn list_and_count<'a>(
185 &'a self,
186 pool: &'a Pool<Postgres>,
187 count: bool,
188 params: &'a ModelListParams,
189 ) -> impl Future<Output = Result<serde_json::Value>> + Send + 'a {
190 async move {
191 if count {
192 let (n, items) =
193 tokio::try_join!(self.count(pool, params), self.list(pool, params))?;
194 Ok(json!({ "count": n, "items": items }))
195 } else {
196 let items = self.list(pool, params).await?;
197 Ok(json!({ "count": -1_i64, "items": items }))
198 }
199 }
200 }
201 fn search_options<'a>(
202 &'a self,
203 _pool: &'a Pool<Postgres>,
204 _keyword: Option<String>,
205 ) -> impl Future<Output = Result<Vec<SchemaOption>>> + Send + 'a {
206 async { Ok(vec![]) }
207 }
208}