1mod query;
8
9use adminx_core::storage::{
10 set_storage, CreateOutcome, ListPage, QueryOptions, Storage, StorageError,
11};
12use async_trait::async_trait;
13use sea_orm::sea_query::{Alias, Expr, Query};
14use sea_orm::{
15 ConnectOptions, ConnectionTrait, Database, DatabaseConnection, DbBackend, ExecResult,
16 FromQueryResult, JsonValue, StatementBuilder,
17};
18use serde_json::{Map, Value};
19
20use query::{
21 build_count_select, build_find_select, build_get_select, build_list_select, id_to_sea_value,
22 json_to_sea_value, value_expr,
23};
24
25pub struct SeaOrmStorage {
27 conn: DatabaseConnection,
28}
29
30impl SeaOrmStorage {
31 pub fn new(conn: DatabaseConnection) -> Self {
32 Self { conn }
33 }
34
35 fn backend(&self) -> DbBackend {
36 self.conn.get_database_backend()
37 }
38
39 pub async fn execute_sql(&self, sql: &str) -> Result<(), sea_orm::DbErr> {
42 let backend = self.backend();
43 self.conn
44 .execute(sea_orm::Statement::from_string(backend, sql.to_owned()))
45 .await?;
46 Ok(())
47 }
48}
49
50pub async fn connect(database_url: &str) -> Result<SeaOrmStorage, sea_orm::DbErr> {
53 let mut opts = ConnectOptions::new(database_url.to_owned());
54 opts.sqlx_logging(false);
55 let conn = Database::connect(opts).await?;
56 tracing::info!(
57 "✅ adminx-seaorm connected ({:?})",
58 conn.get_database_backend()
59 );
60 Ok(SeaOrmStorage::new(conn))
61}
62
63pub async fn init(database_url: &str) -> Result<(), sea_orm::DbErr> {
65 let storage = connect(database_url).await?;
66 set_storage(Box::new(storage));
67 Ok(())
68}
69
70pub async fn seed(database_url: &str, statements: &[&str]) -> Result<u64, StorageError> {
74 let store = connect(database_url)
75 .await
76 .map_err(|e| StorageError::Backend(e.to_string()))?;
77 let mut total = 0u64;
78 for stmt in statements {
79 total += store.execute_raw(stmt).await?;
80 }
81 Ok(total)
82}
83
84fn last_insert_id(backend: DbBackend, res: &ExecResult) -> Option<String> {
85 match backend {
86 DbBackend::MySql | DbBackend::Sqlite => Some(res.last_insert_id().to_string()),
87 _ => None,
88 }
89}
90
91fn map_err(e: sea_orm::DbErr) -> StorageError {
92 StorageError::Backend(e.to_string())
93}
94
95#[async_trait]
96impl Storage for SeaOrmStorage {
97 async fn list(&self, table: &str, opts: &QueryOptions) -> Result<ListPage, StorageError> {
98 let backend = self.backend();
99
100 let count_stmt =
104 StatementBuilder::build(&build_count_select(table, &opts.filters), &backend);
105 let total: u64 = match self.conn.query_one(count_stmt).await.map_err(map_err)? {
106 Some(qr) => qr.try_get::<i64>("", "count").map_err(map_err)?.max(0) as u64,
107 None => 0,
108 };
109
110 let select = build_list_select(
111 table,
112 opts.per_page,
113 opts.offset(),
114 &opts.sort_by,
115 opts.sort_desc,
116 &opts.filters,
117 );
118 let stmt = StatementBuilder::build(&select, &backend);
119 let rows = JsonValue::find_by_statement(stmt)
120 .all(&self.conn)
121 .await
122 .map_err(map_err)?;
123
124 Ok(ListPage { rows, total })
125 }
126
127 async fn get(&self, table: &str, pk: &str, id: &str) -> Result<Option<Value>, StorageError> {
128 let backend = self.backend();
129 let stmt = StatementBuilder::build(&build_get_select(table, pk, id), &backend);
130 JsonValue::find_by_statement(stmt)
131 .one(&self.conn)
132 .await
133 .map_err(map_err)
134 }
135
136 async fn find_one_by(
137 &self,
138 table: &str,
139 column: &str,
140 value: &str,
141 ) -> Result<Option<Value>, StorageError> {
142 let backend = self.backend();
143 let stmt = StatementBuilder::build(&build_find_select(table, column, value), &backend);
144 JsonValue::find_by_statement(stmt)
145 .one(&self.conn)
146 .await
147 .map_err(map_err)
148 }
149
150 async fn create(
151 &self,
152 table: &str,
153 data: Map<String, Value>,
154 ) -> Result<CreateOutcome, StorageError> {
155 let mut insert = Query::insert();
156 insert.into_table(Alias::new(table));
157 insert.columns(data.keys().map(Alias::new));
158 let values = data.values().map(|v| value_expr(json_to_sea_value(v)));
159 insert
160 .values(values)
161 .map_err(|e| StorageError::Backend(e.to_string()))?;
162
163 let backend = self.backend();
164 let stmt = StatementBuilder::build(&insert, &backend);
165 let res = self.conn.execute(stmt).await.map_err(map_err)?;
166
167 Ok(CreateOutcome {
168 last_insert_id: last_insert_id(backend, &res),
169 })
170 }
171
172 async fn update(
173 &self,
174 table: &str,
175 pk: &str,
176 id: &str,
177 data: Map<String, Value>,
178 ) -> Result<u64, StorageError> {
179 let mut update = Query::update();
180 update.table(Alias::new(table));
181 for (col, val) in &data {
182 update.value(Alias::new(col), json_to_sea_value(val));
183 }
184 update.and_where(Expr::col(Alias::new(pk)).eq(id_to_sea_value(id)));
185
186 let backend = self.backend();
187 let stmt = StatementBuilder::build(&update, &backend);
188 let res = self.conn.execute(stmt).await.map_err(map_err)?;
189 Ok(res.rows_affected())
190 }
191
192 async fn delete(
193 &self,
194 table: &str,
195 pk: &str,
196 id: &str,
197 soft: bool,
198 ) -> Result<u64, StorageError> {
199 let backend = self.backend();
200 let stmt = if soft {
201 let mut update = Query::update();
202 update
203 .table(Alias::new(table))
204 .value(Alias::new("deleted"), true)
205 .and_where(Expr::col(Alias::new(pk)).eq(id_to_sea_value(id)));
206 StatementBuilder::build(&update, &backend)
207 } else {
208 let mut delete = Query::delete();
209 delete
210 .from_table(Alias::new(table))
211 .and_where(Expr::col(Alias::new(pk)).eq(id_to_sea_value(id)));
212 StatementBuilder::build(&delete, &backend)
213 };
214
215 let res = self.conn.execute(stmt).await.map_err(map_err)?;
216 Ok(res.rows_affected())
217 }
218
219 async fn execute_raw(&self, statement: &str) -> Result<u64, StorageError> {
220 let backend = self.backend();
221 let res = self
222 .conn
223 .execute(sea_orm::Statement::from_string(backend, statement.to_owned()))
224 .await
225 .map_err(map_err)?;
226 Ok(res.rows_affected())
227 }
228
229 async fn health(&self) -> bool {
230 self.conn.ping().await.is_ok()
231 }
232}