1use super::{
16 Error, JsonSnafu, ModelListParams, Schema, SchemaAllowCreate, SchemaAllowEdit, SchemaOption,
17 SchemaOptionValue, SchemaType, SchemaView, SqlxSnafu, format_datetime,
18};
19use serde::{Deserialize, Serialize};
20use snafu::ResultExt;
21use sqlx::FromRow;
22use sqlx::{Pool, Postgres, QueryBuilder};
23use std::collections::HashMap;
24use tibba_model::Model;
25use time::PrimitiveDateTime;
26
27type Result<T> = std::result::Result<T, Error>;
28
29#[derive(FromRow)]
30struct TokenRechargeSchema {
31 id: i64,
32 user_id: i64,
33 amount: i64,
34 source: i16,
35 order_id: String,
36 expired_at: Option<PrimitiveDateTime>,
37 remark: String,
38 created_by: i64,
39 created: PrimitiveDateTime,
40 modified: PrimitiveDateTime,
41}
42
43#[derive(Debug, Clone, Deserialize, Serialize)]
44pub struct TokenRecharge {
45 pub id: i64,
46 pub user_id: i64,
47 pub amount: i64,
48 pub source: i16,
49 pub order_id: String,
50 pub expired_at: Option<String>,
51 pub remark: String,
52 pub created_by: i64,
53 pub created: String,
54 pub modified: String,
55}
56
57impl From<TokenRechargeSchema> for TokenRecharge {
58 fn from(s: TokenRechargeSchema) -> Self {
59 Self {
60 id: s.id,
61 user_id: s.user_id,
62 amount: s.amount,
63 source: s.source,
64 order_id: s.order_id,
65 expired_at: s.expired_at.map(format_datetime),
66 remark: s.remark,
67 created_by: s.created_by,
68 created: format_datetime(s.created),
69 modified: format_datetime(s.modified),
70 }
71 }
72}
73
74#[derive(Debug, Clone, Deserialize)]
75pub struct TokenRechargeInsertParams {
76 pub user_id: i64,
77 pub amount: i64,
78 pub source: i16,
79 pub order_id: Option<String>,
80 pub expired_at: Option<String>,
81 pub remark: Option<String>,
82 pub created_by: Option<i64>,
83}
84
85#[derive(Default)]
86pub struct TokenRechargeModel {}
87
88impl TokenRechargeModel {
89 pub async fn list_by_user(
91 &self,
92 pool: &Pool<Postgres>,
93 user_id: i64,
94 page: u64,
95 limit: u64,
96 ) -> Result<Vec<TokenRecharge>> {
97 let limit = limit.min(200);
98 let offset = (page.max(1) - 1) * limit;
99 let rows = sqlx::query_as::<_, TokenRechargeSchema>(
100 r#"SELECT * FROM token_recharges WHERE user_id = $1 AND deleted_at IS NULL ORDER BY id DESC LIMIT $2 OFFSET $3"#,
101 )
102 .bind(user_id)
103 .bind(limit as i64)
104 .bind(offset as i64)
105 .fetch_all(pool)
106 .await
107 .context(SqlxSnafu)?;
108 Ok(rows.into_iter().map(Into::into).collect())
109 }
110
111 pub async fn get_by_order_id(
113 &self,
114 pool: &Pool<Postgres>,
115 order_id: &str,
116 ) -> Result<Option<TokenRecharge>> {
117 let result = sqlx::query_as::<_, TokenRechargeSchema>(
118 r#"SELECT * FROM token_recharges WHERE order_id = $1 AND deleted_at IS NULL LIMIT 1"#,
119 )
120 .bind(order_id)
121 .fetch_optional(pool)
122 .await
123 .context(SqlxSnafu)?;
124 Ok(result.map(Into::into))
125 }
126}
127
128impl Model for TokenRechargeModel {
129 type Output = TokenRecharge;
130 fn new() -> Self {
131 Self::default()
132 }
133
134 async fn schema_view(&self, _pool: &Pool<Postgres>) -> SchemaView {
135 let source_options = vec![
136 SchemaOption {
137 label: "购买".to_string(),
138 value: SchemaOptionValue::Integer(1),
139 },
140 SchemaOption {
141 label: "赠送".to_string(),
142 value: SchemaOptionValue::Integer(2),
143 },
144 SchemaOption {
145 label: "退款".to_string(),
146 value: SchemaOptionValue::Integer(3),
147 },
148 SchemaOption {
149 label: "管理员调整".to_string(),
150 value: SchemaOptionValue::Integer(4),
151 },
152 ];
153 SchemaView {
154 schemas: vec![
155 Schema::new_id(),
156 Schema {
157 filterable: true,
158 ..Schema::new_user_search("user_id")
159 },
160 Schema {
161 name: "amount".to_string(),
162 category: SchemaType::Number,
163 required: true,
164 ..Default::default()
165 },
166 Schema {
167 name: "source".to_string(),
168 category: SchemaType::Number,
169 required: true,
170 options: Some(source_options),
171 filterable: true,
172 ..Default::default()
173 },
174 Schema {
175 name: "order_id".to_string(),
176 category: SchemaType::String,
177 ..Default::default()
178 },
179 Schema {
180 name: "expired_at".to_string(),
181 category: SchemaType::Date,
182 ..Default::default()
183 },
184 Schema::new_remark(),
185 Schema {
186 name: "created_by".to_string(),
187 category: SchemaType::Number,
188 read_only: true,
189 ..Default::default()
190 },
191 Schema::new_created(),
192 Schema::new_filterable_modified(),
193 ],
194 allow_edit: SchemaAllowEdit {
195 roles: vec!["su".to_string(), "admin".to_string()],
196 ..Default::default()
197 },
198 allow_create: SchemaAllowCreate {
199 roles: vec!["su".to_string(), "admin".to_string()],
200 ..Default::default()
201 },
202 }
203 }
204
205 async fn insert(&self, pool: &Pool<Postgres>, data: serde_json::Value) -> Result<u64> {
206 let p: TokenRechargeInsertParams = serde_json::from_value(data).context(JsonSnafu)?;
207 let row: (i64,) = sqlx::query_as(
208 r#"INSERT INTO token_recharges (user_id, amount, source, order_id, remark, created_by)
209 VALUES ($1, $2, $3, $4, $5, $6) RETURNING id"#,
210 )
211 .bind(p.user_id)
212 .bind(p.amount)
213 .bind(p.source)
214 .bind(p.order_id.unwrap_or_default())
215 .bind(p.remark.unwrap_or_default())
216 .bind(p.created_by.unwrap_or(0))
217 .fetch_one(pool)
218 .await
219 .context(SqlxSnafu)?;
220 Ok(row.0 as u64)
221 }
222
223 async fn get_by_id(&self, pool: &Pool<Postgres>, id: u64) -> Result<Option<Self::Output>> {
224 let result = sqlx::query_as::<_, TokenRechargeSchema>(
225 r#"SELECT * FROM token_recharges WHERE id = $1 AND deleted_at IS NULL"#,
226 )
227 .bind(id as i64)
228 .fetch_optional(pool)
229 .await
230 .context(SqlxSnafu)?;
231 Ok(result.map(Into::into))
232 }
233
234 async fn delete_by_id(&self, pool: &Pool<Postgres>, id: u64) -> Result<()> {
235 sqlx::query(
236 r#"UPDATE token_recharges SET deleted_at = NOW() WHERE id = $1 AND deleted_at IS NULL"#,
237 )
238 .bind(id as i64)
239 .execute(pool)
240 .await
241 .context(SqlxSnafu)?;
242 Ok(())
243 }
244
245 async fn count(&self, pool: &Pool<Postgres>, params: &ModelListParams) -> Result<i64> {
246 let mut qb: QueryBuilder<Postgres> =
247 QueryBuilder::new("SELECT COUNT(*) FROM token_recharges");
248 self.push_conditions(&mut qb, params)?;
249 let row: (i64,) = qb
250 .build_query_as()
251 .fetch_one(pool)
252 .await
253 .context(SqlxSnafu)?;
254 Ok(row.0)
255 }
256
257 async fn list(
258 &self,
259 pool: &Pool<Postgres>,
260 params: &ModelListParams,
261 ) -> Result<Vec<Self::Output>> {
262 let mut qb: QueryBuilder<Postgres> = QueryBuilder::new("SELECT * FROM token_recharges");
263 self.push_conditions(&mut qb, params)?;
264 params.push_pagination(&mut qb);
265 let rows = qb
266 .build_query_as::<TokenRechargeSchema>()
267 .fetch_all(pool)
268 .await
269 .context(SqlxSnafu)?;
270 Ok(rows.into_iter().map(Into::into).collect())
271 }
272
273 fn push_filter_conditions<'args>(
274 &self,
275 qb: &mut QueryBuilder<'args, Postgres>,
276 filters: &HashMap<String, String>,
277 ) -> Result<()> {
278 if let Some(user_id) = filters.get("user_id") {
279 if let Ok(v) = user_id.parse::<i64>() {
280 qb.push(" AND user_id = ").push_bind(v);
281 }
282 }
283 if let Some(source) = filters.get("source") {
284 if let Ok(v) = source.parse::<i16>() {
285 qb.push(" AND source = ").push_bind(v);
286 }
287 }
288 Ok(())
289 }
290}