refactoring the crud macro and using the new where dsl syntax

This commit is contained in:
zhongqin
2026-05-21 19:25:01 +08:00
parent 627c709d3a
commit fc3e4e1980
49 changed files with 1849 additions and 2772 deletions
+284
View File
@@ -0,0 +1,284 @@
# CRUD Where DSL 设计
## 背景
当前 `crud_query_paged!` 要求用户手写原始 SQL:
```rust
crud_query_paged!(pool, Order,
data_sql: "SELECT * FROM orders WHERE user_id = ?{tenant} ORDER BY created_at DESC",
count_sql: "SELECT COUNT(*) FROM orders WHERE user_id = ?{tenant}",
binds: [user_id],
tenant: tenant_id,
page: page,
page_size: page_size
)
```
### 问题
1. **占位符不兼容** — `?` 在 PostgreSQL 下必须写成 `$N`,用户手写 `?` 会导致 PG 运行时报错
2. **`{tenant}` hack** — 字符串替换方式注入租户条件,脆弱且不优雅
3. **无编译时校验** — 列名拼写错误只能在运行时发现
4. **跨库语法陷阱** — 用户可能无意中写了 `ILIKE`、`::text`、`RETURNING` 等 PG 特有语法
## 设计目标
1. 用户**不再写原始 SQL 的 WHERE 子句**,改用结构化 DSL
2. 宏完全控制 SQL 生成,自动适配 `$N` / `?N` / `?`
3. 编译时校验列名
4. `{tenant}` 自动注入,用户不感知
5. 覆盖 80% 的常见查询场景,剩余 20% 用 `crud_query!` + `Driver::ph()` 手写
## DSL 语法
### 基本形式
```
("column", value) → col = ?
("column", OP, value) → col OP ?
```
### 逻辑组合
```
AND( cond1, cond2, ... ) → (cond1 AND cond2 AND ...)
OR( cond1, cond2, ... ) → (cond1 OR cond2 OR ...)
```
可任意嵌套:
```
AND( ("status", 1), OR(("role", "admin"), ("role", "editor")) )
```
生成 SQL:
```sql
(status = ?) AND ((role = ?) OR (role = ?))
```
### 运算符
| 运算符 | SQL | 元素 | 示例 |
|---|---|---|---|
| `EQ`(默认,可省略) | `= ?` | 2-tuple 或 3-tuple | `("id", 1)` 或 `("id", EQ, 1)` |
| `NEQ` | `!= ?` | 3-tuple | `("status", NEQ, "deleted")` |
| `GT` / `GTE` | `> ?` / `>= ?` | 3-tuple | `("amount", GT, 100)` |
| `LT` / `LTE` | `< ?` / `<= ?` | 3-tuple | `("created_at", LTE, now)` |
| `LIKE` | `LIKE ?` | 3-tuple | `("title", LIKE, "%rust%")` |
| `NOT_LIKE` | `NOT LIKE ?` | 3-tuple | `("title", NOT_LIKE, "%spam%")` |
| `IN` | `IN (?,?,?)` | 3-tuple,value 为 Vec | `("status", IN, vec!["a","b"])` |
| `NOT_IN` | `NOT IN (?,?,?)` | 3-tuple,value 为 Vec | `("status", NOT_IN, vec!["x"])` |
| `IS_NULL` | `IS NULL` | 2-tuple,value 为 `()` | `("deleted_at", ())` |
| `NOT_NULL` | `IS NOT NULL` | 特殊标记 | 待定 |
### 可选条件(动态 WHERE)
用 `Option` 包装值,`None` 时跳过该条件:
```rust
// status 为 None 时不加入 WHERE
where: AND(("user_id", uid), opt!("status", status))
```
`opt!` 宏在编译时展开为条件绑定代码:
```rust
// 生成的代码
if let Some(ref __wv) = status {
__ph_idx += 1;
__where_sql.push_str(&format!(" AND status = {}", __ph(__ph_idx)));
}
```
### 完整调用示例
迁移前(手写 SQL):
```rust
crud_query_paged!(pool, Comment,
data_sql: "SELECT * FROM comments WHERE post_id = ? AND status = ?{tenant} ORDER BY created_at ASC",
count_sql: "SELECT COUNT(*) FROM comments WHERE post_id = ? AND status = ?{tenant}",
binds: [post_id, CommentStatus::Approved],
tenant: tenant_id,
page: page,
page_size: page_size
)
```
迁移后(DSL):
```rust
crud_query_paged!(pool, Comment,
table: "comments",
where: AND(("post_id", post_id), ("status", CommentStatus::Approved)),
order_by: "created_at ASC",
page: page,
page_size: page_size,
tenant: tenant_id
)
```
- 无 `data_sql` / `count_sql` / `binds` — 宏从 DSL 自动生成
- 无 `{tenant}` — `tenant:` 参数自动注入
- 无 `?` — 占位符由宏根据 dialect 自动生成
## 宏 API 对比
### 现有 API(保留)
```rust
crud_query_paged!(pool, Type,
data_sql: "...", // 用户手写 SQL,含 ? 和 {tenant}
count_sql: "...", // 用户手写 SQL
binds: [val1, val2], // 绑定值
where: [...], // 可选的动态条件
tenant: expr,
page: expr,
page_size: expr
)
```
### 新 API(推荐)
```rust
crud_query_paged!(pool, Type,
table: "orders", // 表名
where: AND(("user_id", uid), opt!("status", status)), // DSL 条件
order_by: "created_at DESC", // 排序
tenant: expr, // 自动注入租户
page: expr,
page_size: expr
)
```
**解析策略**:宏检测第一个命名参数。如果是 `data_sql:` → 走旧路径;如果是 `table:` → 走新路径。向后兼容。
## 现有调用迁移分析
| # | 文件 | 现有 WHERE | DSL 等价 |
|---|---|---|---|
| 1 | `wallet_transaction.rs` | `wallet_id = ?` | `("wallet_id", wallet_id)` |
| 2 | `wallet_transaction.rs` | `user_id = ?` | `("user_id", user_id)` |
| 3 | `wallet_transaction.rs` | `1=1{tenant}` | 无 where,仅 tenant |
| 4 | `page.rs` | `status = ?{tenant}` | `("status", status)` |
| 5 | `order.rs` | `user_id = ?{tenant}` | `("user_id", user_id)` |
| 6 | `media.rs` | `user_id = ?{tenant}` | `("user_id", user_id)` |
| 7 | `payment_order.rs` | `user_id = ?{tenant}` | `("user_id", user_id)` |
| 8 | `comment.rs` | `post_id = ? AND status = ?{tenant}` | `AND(("post_id", post_id), ("status", status))` |
| 9-28 | 其他 20 处 | `1=1{tenant}` + 可选 where | 无 where + `where: [...]` |
**100% 现有用法可迁移。**
## 不适合 DSL 的场景
以下场景继续使用 `crud_query!` + `Driver::ph()` 手写:
| 场景 | 原因 | 示例 |
|---|---|---|
| 子查询 | `WHERE id IN (SELECT ...)` | `worker/job_queue.rs` dequeue |
| 聚合 | `GROUP BY` / `HAVING` / `SUM()` | `models/payment_refund.rs` sum_refunded |
| CASE 表达式 | `CASE WHEN ... THEN ... END` | `models/wallet_outbox.rs` mark_failed |
| 动态表名 | `FROM {dynamic_table}` | `services/stats.rs` |
| 动态列名 | `SET {col} = ?` | `models/order.rs` timestamp_col |
| 复杂 JOIN | 多表 JOIN + 动态 WHERE | `models/post.rs` |
| CAS 更新 | `version = version + 1` | `models/order.rs` tx_update_status_cas |
## 实现计划
### Phase 1: 解析器
在 `raisfast-derive/src/crud.rs` 中新增 DSL 解析:
- `WhereExpr` 枚举:`Condition` / `And` / `Or` / `Optional`
- `Operator` 枚举:`EQ` / `NEQ` / `GT` / `GTE` / `LT` / `LTE` / `LIKE` / `IN` / `IS_NULL` ...
- `Condition` 结构体:`(col, [op,] value)`
### Phase 2: SQL 生成
`WhereExpr` → SQL 字符串 + 绑定参数列表:
- 递归遍历 AST,生成 `col = ?N` / `col > ?N` 等
- 自动追踪 `__ph_idx`
- tenant 条件自动追加
### Phase 3: 编译时校验
对 DSL 中的每个列名调用 `validate_column(table, col)`,编译时报错拼写错误。
### Phase 4: 旧 API 兼容
- `data_sql:` 路径保留,加编译警告建议迁移
- `crud_join_paged!` 同步支持 DSL
### Phase 5: 迁移现有调用
将 28 处 `crud_query_paged!` 从手写 SQL 迁移到 DSL。
## Rust 类型表达(待定)
在 proc-macro 中解析 DSL 有几种方案:
### 方案 A: Token 解析
```rust
// 用户写法
where: AND(("post_id", post_id), ("status", status))
// proc-macro 解析 TokenStream
// 识别 AND(...) / OR(...) / (...) 三种模式
```
优点:语法简洁,接近 SQL 思维。
难点:Rust 宏里 `(expr, expr)` 是 tuple 字面量,需要自定义解析器。
### 方案 B: 数组语法
```rust
// 用户写法
where: [AND, [("post_id", post_id), ("status", status)]]
// proc-macro 更容易解析
```
优点:解析简单。
缺点:不直观。
### 方案 C: 关键字语法
```rust
// 用户写法
where: "post_id" => post_id, and: ["status" => status]
// 与现有 crud_find! 语法一致
```
优点:已有先例,解析器已存在。
缺点:不支持 OR / 嵌套 / 运算符。
**推荐方案 A**,因为它最接近 SQL 表达力,且 proc-macro 有完整的 TokenStream 解析能力。
## 占位符生成规则
| Dialect | `EQ` | `GT` | `IN(vec![a,b,c])` | `IS_NULL` | `LIKE` |
|---|---|---|---|---|---|
| SQLite | `?1` | `?2` | `IN (?3,?4,?5)` | `IS NULL` | `LIKE ?6` |
| PostgreSQL | `$1` | `$2` | `IN ($3,$4,$5)` | `IS NULL` | `LIKE $6` |
| MySQL | `?` | `?` | `IN (?,?,?)` | `IS NULL` | `LIKE ?` |
所有占位符由 `Dialect::ph(idx)` 生成,idx 由宏在遍历 AST 时自动递增。
## 与 `crud_join_paged!` 的关系
`crud_join_paged!` 已自行生成 WHERE SQL(不依赖用户手写),但只有 `=` 运算符。
DSL 可同时应用于两个宏,统一条件表达方式。
## 风险和缓解
| 风险 | 缓解 |
|---|---|
| DSL 解析复杂度 | 递归下降解析器,参考 `expand_find` 现有的 `and:` 解析 |
| proc-macro 编译变慢 | DSL 只在 `crud_query_paged!` 中使用,影响面小 |
| 用户学习成本 | DSL 元素与 SQL 一一对应,直觉易学 |
| 不支持的 SQL 回退 | 保留 `crud_query!` + `Driver::ph()` 手写路径 |
+798 -2417
View File
File diff suppressed because it is too large Load Diff
+22 -30
View File
@@ -21,16 +21,17 @@
//!
//! | Macro | SQL operation |
//! |-------|---------------|
//! | `crud_delete!` | `DELETE FROM ... WHERE col = ?` |
//! | `crud_delete!` | `DELETE FROM ... WHERE WhereExpr` |
//! | `crud_insert!` | `INSERT INTO ... (...) VALUES (...)` |
//! | `crud_scalar!` | `SELECT scalar ...` |
//! | `crud_query!` | `SELECT ...` via `query_as` |
//! | `crud_find!` | `SELECT cols FROM ... WHERE col = ?` → `fetch_optional` |
//! | `crud_find!` | `SELECT cols FROM ... WHERE WhereExpr` → `fetch_optional` |
//! | `crud_find_one!` | same → `fetch_one` |
//! | `crud_find_all!` | same → `fetch_all` |
//! | `crud_list!` | `SELECT cols FROM ...` → `fetch_all` (no WHERE) |
//! | `crud_update!` | `UPDATE ... SET ... WHERE pk = ?` |
//! | `crud_count!` | `SELECT COUNT(*) FROM ... WHERE col = ?` |
//! | `crud_update!` | `UPDATE ... SET ... WHERE WhereExpr` |
//! | `crud_count!` | `SELECT COUNT(*) FROM ... WHERE WhereExpr` |
//! | `crud_exists!` | `SELECT EXISTS(SELECT 1 ... WHERE WhereExpr)` |
//! | `crud_query_paged!` | paginated data + COUNT |
//! | `crud_join_paged!` | paginated JOIN + COUNT |
//!
@@ -52,6 +53,7 @@ mod aspect_service;
mod crud;
mod event_meta;
mod schema;
mod where_dsl;
use proc_macro::TokenStream;
@@ -64,10 +66,10 @@ pub fn derive_event_meta(input: TokenStream) -> TokenStream {
event_meta::derive_event_meta(input)
}
/// `crud_delete!(pool, "table", "col" => val [, tenant: expr, and: ...])`
/// `crud_delete!(pool, "table", where: WhereExpr [, tenant: expr])`
///
/// Generates `DELETE FROM table WHERE col = ?1` via `sqlx::query!()`.
/// When `tenant:` is provided, adds `AND tenant_id = ?` filter.
/// Generates `DELETE FROM table WHERE ...` via `sqlx::query()`.
/// Uses Where DSL for conditions. When `tenant:` is provided, adds `AND tenant_id = ?` filter.
#[proc_macro]
pub fn crud_delete(input: TokenStream) -> TokenStream {
crud::crud_delete(input)
@@ -90,17 +92,17 @@ pub fn crud_scalar(input: TokenStream) -> TokenStream {
crud::crud_scalar(input)
}
/// `crud_select!(pool, "table", ["col1", "col2"], "where_col" => val [, tenant: expr, and: ...])`
/// `crud_select!(pool, "table", ["col1", "col2"], where: WhereExpr [, tenant: expr])`
///
/// Generates `SELECT col1, col2 FROM table WHERE where_col = ?` via `sqlx::query_as`.
/// Generates `SELECT col1, col2 FROM table WHERE ...` via `sqlx::query_as`.
#[proc_macro]
pub fn crud_select(input: TokenStream) -> TokenStream {
crud::crud_select(input)
}
/// `crud_join!(pool, Type, select: [...], from: "...", joins: [...], where: "col" => val [, tenant: expr, method: fetch_all])`
/// `crud_join!(pool, Type, select: [...], from: "...", joins: [...], where: WhereExpr [, tenant: expr, method: fetch_all, order_by: "...", limit: expr, offset: expr])`
///
/// Generates a JOIN query with optional tenant filtering.
/// Generates a JOIN query with optional Where DSL conditions and tenant filtering.
#[proc_macro]
pub fn crud_join(input: TokenStream) -> TokenStream {
crud::crud_join(input)
@@ -114,9 +116,9 @@ pub fn crud_join_paged(input: TokenStream) -> TokenStream {
crud::crud_join_paged(input)
}
/// `crud_count!(pool, "table", "col" => val [, tenant: expr, and: ["c" => v, ...]])`
/// `crud_count!(pool, "table", where: WhereExpr [, tenant: expr])`
///
/// `SELECT COUNT(*) FROM table WHERE col = ? [AND c = ? ...]` → `i64`.
/// `SELECT COUNT(*) FROM table WHERE ...` → `i64`.
#[proc_macro]
pub fn crud_count(input: TokenStream) -> TokenStream {
crud::crud_count(input)
@@ -130,9 +132,9 @@ pub fn crud_query(input: TokenStream) -> TokenStream {
crud::crud_query(input)
}
/// `crud_find!(pool, "table", Type, "col" => val [, tenant: expr, and: ...])`
/// `crud_find!(pool, "table", Type, where: WhereExpr [, tenant: expr, order_by: "expr"])`
///
/// `SELECT {all_columns} FROM table WHERE col = ?` → `fetch_optional`.
/// `SELECT {all_columns} FROM table WHERE ...` → `fetch_optional`.
/// Column list is generated from schema (replaces `SELECT *`).
#[proc_macro]
pub fn crud_find(input: TokenStream) -> TokenStream {
@@ -176,9 +178,9 @@ pub fn check_schema(input: TokenStream) -> TokenStream {
crud::check_schema(input)
}
/// `crud_exists!(pool, "table", "col" => val [, tenant: expr, and: ["c" => v, ...]])`
/// `crud_exists!(pool, "table", where: WhereExpr [, tenant: expr])`
///
/// `SELECT EXISTS(SELECT 1 FROM table WHERE col = ? [...])` → `bool`.
/// `SELECT EXISTS(SELECT 1 FROM table WHERE ...)` → `bool`.
/// Uses `sqlx::query_scalar` with compile-time verified SQL.
#[proc_macro]
pub fn crud_exists(input: TokenStream) -> TokenStream {
@@ -194,20 +196,10 @@ pub fn crud_upsert(input: TokenStream) -> TokenStream {
crud::crud_upsert(input)
}
/// `crud_patch!(pool, "table", bind: [...], optional: [...], raw: [...], where: "pk" => val, and: [...] [, tenant: tid])`
/// `crud_update!(pool, "table", bind: [...], optional: [...], raw: [...], where: WhereExpr [, tenant: tid])`
///
/// Dynamic partial UPDATE — only non-None `optional:` fields are included in SET.
/// `bind:` fields are always set. `raw:` fields use SQL expressions.
/// Generates runtime `sqlx::query()`.
#[proc_macro]
pub fn crud_patch(input: TokenStream) -> TokenStream {
crud::crud_patch(input)
}
/// `crud_update!(pool, "table", bind: [...], raw: [...], where: "pk" => val, and: [...] [, tenant: tid])`
///
/// Generates a runtime `sqlx::query()` UPDATE.
/// Values are pre-bound to `let` variables to avoid E0716 temporary lifetime issues.
/// Generates a runtime `sqlx::query()` UPDATE. Supports `bind:` (always-set),
/// `optional:` (set only when Some), `raw:` (SQL expressions).
#[proc_macro]
pub fn crud_update(input: TokenStream) -> TokenStream {
crud::crud_update(input)
+471
View File
@@ -0,0 +1,471 @@
use syn::parse::discouraged::Speculative;
use crate::schema::Dialect;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CmpOp {
Eq,
Neq,
Gt,
Gte,
Lt,
Lte,
Like,
NotLike,
In,
NotIn,
IsNull,
NotNull,
}
const OP_KEYWORDS: &[&str] = &[
"EQ", "NEQ", "GT", "GTE", "LT", "LTE", "LIKE", "NOT_LIKE", "IN", "NOT_IN", "IS_NULL",
"NOT_NULL",
];
fn is_operator_keyword(s: &str) -> bool {
OP_KEYWORDS.contains(&s)
}
fn parse_op(s: &str, span: proc_macro2::Span) -> syn::Result<CmpOp> {
match s {
"EQ" => Ok(CmpOp::Eq),
"NEQ" => Ok(CmpOp::Neq),
"GT" => Ok(CmpOp::Gt),
"GTE" => Ok(CmpOp::Gte),
"LT" => Ok(CmpOp::Lt),
"LTE" => Ok(CmpOp::Lte),
"LIKE" => Ok(CmpOp::Like),
"NOT_LIKE" => Ok(CmpOp::NotLike),
"IN" => Ok(CmpOp::In),
"NOT_IN" => Ok(CmpOp::NotIn),
"IS_NULL" => Ok(CmpOp::IsNull),
"NOT_NULL" => Ok(CmpOp::NotNull),
_ => Err(syn::Error::new(span, format!("unknown operator: {s}"))),
}
}
#[derive(Debug)]
#[allow(clippy::large_enum_variant)]
pub enum WhereExpr {
Condition {
col: String,
col_span: proc_macro2::Span,
op: CmpOp,
value: Option<syn::Expr>,
},
And(Vec<WhereExpr>),
Or(Vec<WhereExpr>),
}
impl syn::parse::Parse for WhereExpr {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let lookahead = input.lookahead1();
if lookahead.peek(syn::Ident) {
let ident: syn::Ident = input.parse()?;
match ident.to_string().as_str() {
"AND" => {
let content;
syn::parenthesized!(content in input);
let mut exprs = Vec::new();
while !content.is_empty() {
exprs.push(content.parse()?);
let _ = content.parse::<syn::Token![,]>();
}
if exprs.is_empty() {
return Err(syn::Error::new(
ident.span(),
"AND requires at least one condition",
));
}
Ok(WhereExpr::And(exprs))
}
"OR" => {
let content;
syn::parenthesized!(content in input);
let mut exprs = Vec::new();
while !content.is_empty() {
exprs.push(content.parse()?);
let _ = content.parse::<syn::Token![,]>();
}
if exprs.is_empty() {
return Err(syn::Error::new(
ident.span(),
"OR requires at least one condition",
));
}
Ok(WhereExpr::Or(exprs))
}
_ => Err(syn::Error::new(
ident.span(),
"expected AND or OR, or a condition tuple",
)),
}
} else if lookahead.peek(syn::token::Paren) {
let content;
syn::parenthesized!(content in input);
let col: syn::LitStr = content.parse()?;
let _: syn::Token![,] = content.parse()?;
let fork = content.fork();
if let Ok(ident) = fork.parse::<syn::Ident>()
&& is_operator_keyword(&ident.to_string())
{
content.advance_to(&fork);
let op = parse_op(&ident.to_string(), ident.span())?;
if matches!(op, CmpOp::IsNull | CmpOp::NotNull) {
return Ok(WhereExpr::Condition {
col: col.value(),
col_span: col.span(),
op,
value: None,
});
}
let _: syn::Token![,] = content.parse()?;
let value: syn::Expr = content.parse()?;
return Ok(WhereExpr::Condition {
col: col.value(),
col_span: col.span(),
op,
value: Some(value),
});
}
let value: syn::Expr = content.parse()?;
Ok(WhereExpr::Condition {
col: col.value(),
col_span: col.span(),
op: CmpOp::Eq,
value: Some(value),
})
} else {
Err(lookahead.error())
}
}
}
pub struct WhereCodegen {
pub sql: String,
pub binds: Vec<syn::Expr>,
pub next_idx: usize,
pub d: Dialect,
}
impl WhereCodegen {
pub fn new(d: Dialect, start_idx: usize) -> Self {
Self {
sql: String::new(),
binds: Vec::new(),
next_idx: start_idx,
d,
}
}
pub fn generate(&mut self, expr: &WhereExpr) {
match expr {
WhereExpr::And(exprs) => {
self.sql.push('(');
for (i, e) in exprs.iter().enumerate() {
if i > 0 {
self.sql.push_str(" AND ");
}
self.generate(e);
}
self.sql.push(')');
}
WhereExpr::Or(exprs) => {
self.sql.push('(');
for (i, e) in exprs.iter().enumerate() {
if i > 0 {
self.sql.push_str(" OR ");
}
self.generate(e);
}
self.sql.push(')');
}
WhereExpr::Condition { col, op, value, .. } => {
self.sql.push_str(col);
match op {
CmpOp::Eq => {
self.sql.push('=');
self.push_ph();
self.binds.push(value.as_ref().unwrap().clone());
}
CmpOp::Neq => {
self.sql.push_str("!=");
self.push_ph();
self.binds.push(value.as_ref().unwrap().clone());
}
CmpOp::Gt => {
self.sql.push('>');
self.push_ph();
self.binds.push(value.as_ref().unwrap().clone());
}
CmpOp::Gte => {
self.sql.push_str(">=");
self.push_ph();
self.binds.push(value.as_ref().unwrap().clone());
}
CmpOp::Lt => {
self.sql.push('<');
self.push_ph();
self.binds.push(value.as_ref().unwrap().clone());
}
CmpOp::Lte => {
self.sql.push_str("<=");
self.push_ph();
self.binds.push(value.as_ref().unwrap().clone());
}
CmpOp::Like => {
self.sql.push_str(" LIKE ");
self.push_ph();
self.binds.push(value.as_ref().unwrap().clone());
}
CmpOp::NotLike => {
self.sql.push_str(" NOT LIKE ");
self.push_ph();
self.binds.push(value.as_ref().unwrap().clone());
}
CmpOp::In | CmpOp::NotIn => {
let kw = if *op == CmpOp::In { " IN " } else { " NOT IN " };
self.sql.push_str(kw);
self.sql.push('(');
self.push_ph();
self.sql.push(')');
self.binds.push(value.as_ref().unwrap().clone());
}
CmpOp::IsNull => {
self.sql.push_str(" IS NULL");
}
CmpOp::NotNull => {
self.sql.push_str(" IS NOT NULL");
}
}
}
}
}
fn push_ph(&mut self) {
self.sql.push_str(&self.d.ph(self.next_idx));
self.next_idx += 1;
}
}
#[derive(Debug)]
pub enum BindKind {
Static(syn::Expr),
InLoop(syn::Expr),
}
pub struct WhereRuntimeResult {
pub sql_code: proc_macro2::TokenStream,
pub binds: Vec<BindKind>,
}
pub fn generate_where_runtime(expr: &WhereExpr, d: Dialect) -> WhereRuntimeResult {
let mut result = WhereRuntimeResult {
sql_code: proc_macro2::TokenStream::new(),
binds: Vec::new(),
};
build_runtime(expr, d, &mut result);
result
}
fn ph_code(d: Dialect) -> proc_macro2::TokenStream {
match d {
Dialect::Sqlite => quote::quote! { format!("?{}", __ph_idx) },
Dialect::Postgres => quote::quote! { format!("${}", __ph_idx) },
Dialect::Mysql => quote::quote! { "?".to_string() },
}
}
fn build_runtime(expr: &WhereExpr, d: Dialect, result: &mut WhereRuntimeResult) {
match expr {
WhereExpr::And(exprs) => {
result.sql_code.extend(quote::quote! { __where_sql.push('('); });
for (i, e) in exprs.iter().enumerate() {
if i > 0 {
result.sql_code.extend(quote::quote! { __where_sql.push_str(" AND "); });
}
build_runtime(e, d, result);
}
result.sql_code.extend(quote::quote! { __where_sql.push(')'); });
}
WhereExpr::Or(exprs) => {
result.sql_code.extend(quote::quote! { __where_sql.push('('); });
for (i, e) in exprs.iter().enumerate() {
if i > 0 {
result.sql_code.extend(quote::quote! { __where_sql.push_str(" OR "); });
}
build_runtime(e, d, result);
}
result.sql_code.extend(quote::quote! { __where_sql.push(')'); });
}
WhereExpr::Condition { col, op, value, .. } => {
let col_lit = syn::LitStr::new(col, proc_macro2::Span::call_site());
match op {
CmpOp::Eq => {
let val = value.as_ref().unwrap();
let ph = ph_code(d);
result.sql_code.extend(quote::quote! {
__where_sql.push_str(#col_lit);
__where_sql.push('=');
__where_sql.push_str(&{ #ph });
__ph_idx += 1;
});
result.binds.push(BindKind::Static(val.clone()));
}
CmpOp::Neq => {
let val = value.as_ref().unwrap();
let ph = ph_code(d);
result.sql_code.extend(quote::quote! {
__where_sql.push_str(#col_lit);
__where_sql.push_str("!=");
__where_sql.push_str(&{ #ph });
__ph_idx += 1;
});
result.binds.push(BindKind::Static(val.clone()));
}
CmpOp::Gt => {
let val = value.as_ref().unwrap();
let ph = ph_code(d);
result.sql_code.extend(quote::quote! {
__where_sql.push_str(#col_lit);
__where_sql.push('>');
__where_sql.push_str(&{ #ph });
__ph_idx += 1;
});
result.binds.push(BindKind::Static(val.clone()));
}
CmpOp::Gte => {
let val = value.as_ref().unwrap();
let ph = ph_code(d);
result.sql_code.extend(quote::quote! {
__where_sql.push_str(#col_lit);
__where_sql.push_str(">=");
__where_sql.push_str(&{ #ph });
__ph_idx += 1;
});
result.binds.push(BindKind::Static(val.clone()));
}
CmpOp::Lt => {
let val = value.as_ref().unwrap();
let ph = ph_code(d);
result.sql_code.extend(quote::quote! {
__where_sql.push_str(#col_lit);
__where_sql.push('<');
__where_sql.push_str(&{ #ph });
__ph_idx += 1;
});
result.binds.push(BindKind::Static(val.clone()));
}
CmpOp::Lte => {
let val = value.as_ref().unwrap();
let ph = ph_code(d);
result.sql_code.extend(quote::quote! {
__where_sql.push_str(#col_lit);
__where_sql.push_str("<=");
__where_sql.push_str(&{ #ph });
__ph_idx += 1;
});
result.binds.push(BindKind::Static(val.clone()));
}
CmpOp::Like => {
let val = value.as_ref().unwrap();
let ph = ph_code(d);
result.sql_code.extend(quote::quote! {
__where_sql.push_str(#col_lit);
__where_sql.push_str(" LIKE ");
__where_sql.push_str(&{ #ph });
__ph_idx += 1;
});
result.binds.push(BindKind::Static(val.clone()));
}
CmpOp::NotLike => {
let val = value.as_ref().unwrap();
let ph = ph_code(d);
result.sql_code.extend(quote::quote! {
__where_sql.push_str(#col_lit);
__where_sql.push_str(" NOT LIKE ");
__where_sql.push_str(&{ #ph });
__ph_idx += 1;
});
result.binds.push(BindKind::Static(val.clone()));
}
CmpOp::In | CmpOp::NotIn => {
let val = value.as_ref().unwrap();
let kw = if *op == CmpOp::In { " IN " } else { " NOT IN " };
let kw_lit = syn::LitStr::new(kw, proc_macro2::Span::call_site());
let numbered = matches!(d, Dialect::Sqlite | Dialect::Postgres);
let numbered_lit = syn::LitBool::new(numbered, proc_macro2::Span::call_site());
let ph_prefix_lit = syn::LitStr::new(
match d {
Dialect::Postgres => "$",
_ => "?",
},
proc_macro2::Span::call_site(),
);
let bind_idx = result.binds.len();
let in_ident = syn::Ident::new(
&format!("__in_{}", bind_idx),
proc_macro2::Span::call_site(),
);
let fallback = if *op == CmpOp::In { "1=0" } else { "1=1" };
let fallback_lit = syn::LitStr::new(fallback, proc_macro2::Span::call_site());
result.sql_code.extend(quote::quote! {
if !#in_ident.is_empty() {
__where_sql.push_str(#col_lit);
__where_sql.push_str(#kw_lit);
__where_sql.push('(');
let __in_ph: String = if #numbered_lit {
(0..#in_ident.len()).map(|i| format!(concat!(#ph_prefix_lit, "{}"), __ph_idx + i)).collect::<Vec<_>>().join(",")
} else {
(0..#in_ident.len()).map(|_| "?").collect::<Vec<_>>().join(",")
};
__ph_idx += #in_ident.len();
__where_sql.push_str(&__in_ph);
__where_sql.push(')');
} else {
__where_sql.push_str(#col_lit);
__where_sql.push_str(#kw_lit);
__where_sql.push_str(#fallback_lit);
}
});
result.binds.push(BindKind::InLoop(val.clone()));
}
CmpOp::IsNull => {
result.sql_code.extend(quote::quote! {
__where_sql.push_str(#col_lit);
__where_sql.push_str(" IS NULL");
});
}
CmpOp::NotNull => {
result.sql_code.extend(quote::quote! {
__where_sql.push_str(#col_lit);
__where_sql.push_str(" IS NOT NULL");
});
}
}
}
}
}
pub fn validate_where_columns(expr: &WhereExpr, table: &str) -> Option<proc_macro2::TokenStream> {
match expr {
WhereExpr::And(exprs) | WhereExpr::Or(exprs) => {
for e in exprs {
if let Some(err) = validate_where_columns(e, table) {
return Some(err);
}
}
None
}
WhereExpr::Condition { col, col_span, .. } => {
let col_lit = syn::LitStr::new(col, *col_span);
crate::crud::validate_column_inner(table, &col_lit)
}
}
}
+1 -1
View File
@@ -40,7 +40,7 @@ pub async fn seed(
) -> anyhow::Result<()> {
let pool = init_pool(&config.database_url, 1).await?;
if raisfast_derive::crud_exists!(&pool, "users", "username" => username)? {
if raisfast_derive::crud_exists!(&pool, "users", where: ("username", username))? {
println!("seed: admin user already exists ({username}), skipping");
return Ok(());
}
+4 -4
View File
@@ -74,7 +74,7 @@ pub async fn create(
/// Find API Token by token_hash
pub async fn find_by_hash(pool: &crate::db::Pool, token_hash: &str) -> AppResult<Option<ApiToken>> {
raisfast_derive::crud_find!(pool, "api_tokens", ApiToken, "token_hash" => token_hash)
raisfast_derive::crud_find!(pool, "api_tokens", ApiToken, where: ("token_hash", token_hash))
.map_err(Into::into)
}
@@ -107,12 +107,12 @@ pub async fn list_by_user(
/// Find API Token by id
pub async fn find_by_id(pool: &crate::db::Pool, id: SnowflakeId) -> AppResult<Option<ApiToken>> {
raisfast_derive::crud_find!(pool, "api_tokens", ApiToken, "id" => id).map_err(Into::into)
raisfast_derive::crud_find!(pool, "api_tokens", ApiToken, where: ("id", id)).map_err(Into::into)
}
/// Delete API Token by id
pub async fn delete_by_id(pool: &crate::db::Pool, id: SnowflakeId) -> AppResult<()> {
raisfast_derive::crud_delete!(pool, "api_tokens", "id" => id)?;
raisfast_derive::crud_delete!(pool, "api_tokens", where: ("id", id))?;
Ok(())
}
@@ -121,7 +121,7 @@ pub async fn touch_last_used(pool: &crate::db::Pool, id: SnowflakeId) -> AppResu
let now = crate::utils::tz::now_utc();
raisfast_derive::crud_update!(pool, "api_tokens",
bind: ["last_used_at" => now],
where: "id" => id
where: ("id", id)
)?;
Ok(())
}
+4 -6
View File
@@ -58,11 +58,9 @@ pub async fn find_paginated(
) -> AppResult<(Vec<AuditEntry>, i64)> {
let result = raisfast_derive::crud_query_paged!(
pool, AuditEntry,
data_sql: "SELECT * FROM audit_log WHERE 1=1 ORDER BY created_at DESC",
count_sql: "SELECT COUNT(*) FROM audit_log WHERE 1=1",
binds: [],
where: ["tenant_id" => tenant_id, "action" => action, "actor_id" => actor_id],
tenant: None::<&str>,
table: "audit_log",
where: AND(("tenant_id", tenant_id), ("action", action), ("actor_id", actor_id)),
order_by: "created_at DESC",
page: page,
page_size: page_size
);
@@ -71,5 +69,5 @@ pub async fn find_paginated(
/// Find an audit log entry by ID
pub async fn find_by_id(pool: &crate::db::Pool, id: SnowflakeId) -> AppResult<AuditEntry> {
raisfast_derive::crud_find_one!(pool, "audit_log", AuditEntry, "id" => id).map_err(Into::into)
raisfast_derive::crud_find_one!(pool, "audit_log", AuditEntry, where: ("id", id)).map_err(Into::into)
}
+10 -13
View File
@@ -29,7 +29,7 @@ pub async fn find_by_user_id(
pool,
"cart_items",
CartItem,
"user_id" => user_id,
where: ("user_id", user_id),
order_by: "created_at DESC",
tenant: tenant_id
)?)
@@ -47,8 +47,7 @@ pub async fn find_by_user_and_product(
pool,
"cart_items",
CartItem,
"user_id" => user_id,
and: ["product_id" => product_id, "variant_id" => vid],
where: AND(("user_id", user_id), ("product_id", product_id), ("variant_id", vid)),
tenant: tenant_id
)
.map_err(Into::into)
@@ -57,9 +56,7 @@ pub async fn find_by_user_and_product(
pool,
"cart_items",
CartItem,
"user_id" => user_id,
and: ["product_id" => product_id],
and_null: ["variant_id"],
where: AND(("user_id", user_id), ("product_id", product_id), ("variant_id", IS_NULL)),
tenant: tenant_id
)
.map_err(Into::into)
@@ -94,7 +91,7 @@ pub async fn insert(
],
tenant: tenant_id
)?;
raisfast_derive::crud_find_one!(pool, "cart_items", CartItem, "id" => id, tenant: tenant_id)
raisfast_derive::crud_find_one!(pool, "cart_items", CartItem, where: ("id", id), tenant: tenant_id)
.map_err(Into::into)
}
@@ -109,7 +106,7 @@ pub async fn update_quantity(
pool,
"cart_items",
bind: ["quantity" => quantity, "updated_at" => &now],
where: "id" => id,
where: ("id", id),
tenant: tenant_id
)?;
AppError::expect_affected(&result, "cart_item")
@@ -120,7 +117,7 @@ pub async fn find_by_id(
id: SnowflakeId,
tenant_id: Option<&str>,
) -> AppResult<Option<CartItem>> {
Ok(raisfast_derive::crud_find!(pool, "cart_items", CartItem, "id" => id, tenant: tenant_id)?)
Ok(raisfast_derive::crud_find!(pool, "cart_items", CartItem, where: ("id", id), tenant: tenant_id)?)
}
pub async fn delete_by_id(
@@ -128,7 +125,7 @@ pub async fn delete_by_id(
id: SnowflakeId,
tenant_id: Option<&str>,
) -> AppResult<()> {
let result = raisfast_derive::crud_delete!(pool, "cart_items", "id" => id, tenant: tenant_id)?;
let result = raisfast_derive::crud_delete!(pool, "cart_items", where: ("id", id), tenant: tenant_id)?;
AppError::expect_affected(&result, "cart_item")
}
@@ -138,7 +135,7 @@ pub async fn delete_by_user_id(
tenant_id: Option<&str>,
) -> AppResult<()> {
let result =
raisfast_derive::crud_delete!(pool, "cart_items", "user_id" => user_id, tenant: tenant_id)?;
raisfast_derive::crud_delete!(pool, "cart_items", where: ("user_id", user_id), tenant: tenant_id)?;
AppError::expect_affected(&result, "cart_item")
}
@@ -147,7 +144,7 @@ pub async fn tx_delete_by_user_id(
user_id: SnowflakeId,
tenant_id: Option<&str>,
) -> AppResult<()> {
let result = raisfast_derive::crud_delete!(&mut *tx, "cart_items", "user_id" => user_id, tenant: tenant_id)?;
let result = raisfast_derive::crud_delete!(&mut *tx, "cart_items", where: ("user_id", user_id), tenant: tenant_id)?;
AppError::expect_affected(&result, "cart_item")
}
@@ -157,7 +154,7 @@ pub async fn count_by_user(
tenant_id: Option<&str>,
) -> AppResult<i64> {
let count =
raisfast_derive::crud_count!(pool, "cart_items", "user_id" => user_id, tenant: tenant_id)?;
raisfast_derive::crud_count!(pool, "cart_items", where: ("user_id", user_id), tenant: tenant_id)?;
Ok(count)
}
+5 -6
View File
@@ -46,9 +46,8 @@ pub async fn find_paginated(
) -> AppResult<(Vec<Category>, i64)> {
let result = raisfast_derive::crud_query_paged!(
pool, Category,
data_sql: "SELECT * FROM categories WHERE 1=1{tenant} ORDER BY sort_order, name",
count_sql: "SELECT COUNT(*) FROM categories WHERE 1=1{tenant}",
binds: [],
table: "categories",
order_by: "sort_order, name",
tenant: tenant_id,
page: page,
page_size: page_size
@@ -61,7 +60,7 @@ pub async fn find_by_id(
id: SnowflakeId,
tenant_id: Option<&str>,
) -> AppResult<Category> {
raisfast_derive::crud_find_one!(pool, "categories", Category, "id" => id, tenant: tenant_id)
raisfast_derive::crud_find_one!(pool, "categories", Category, where: ("id", id), tenant: tenant_id)
.map_err(Into::into)
}
@@ -121,7 +120,7 @@ pub async fn update(
let now = crate::utils::tz::now_utc();
raisfast_derive::crud_update!(pool, "categories",
bind: ["name" => name, "slug" => slug, "description" => desc, "parent_id" => parent, "sort_order" => sort, "updated_by" => updated_by, "updated_at" => &now],
where: "id" => cat_id,
where: ("id", cat_id),
tenant: tenant_id
)?;
@@ -133,7 +132,7 @@ pub async fn delete(
id: SnowflakeId,
tenant_id: Option<&str>,
) -> AppResult<()> {
let result = raisfast_derive::crud_delete!(pool, "categories", "id" => id, tenant: tenant_id)?;
let result = raisfast_derive::crud_delete!(pool, "categories", where: ("id", id), tenant: tenant_id)?;
AppError::expect_affected(&result, "category")
}
+14 -13
View File
@@ -64,7 +64,7 @@ pub async fn find_by_id(
id: SnowflakeId,
tenant_id: Option<&str>,
) -> AppResult<Option<Comment>> {
Ok(raisfast_derive::crud_find!(pool, "comments", Comment, "id" => id, tenant: tenant_id)?)
Ok(raisfast_derive::crud_find!(pool, "comments", Comment, where: ("id", id), tenant: tenant_id)?)
}
pub async fn create(
@@ -107,7 +107,7 @@ pub async fn find_approved_by_post(
tenant_id: Option<&str>,
) -> AppResult<Vec<Comment>> {
Ok(
raisfast_derive::crud_find_all!(pool, "comments", Comment, "post_id" => post_id, tenant: tenant_id, and: ["status" => CommentStatus::Approved], order_by: "created_at ASC")?,
raisfast_derive::crud_find_all!(pool, "comments", Comment, where: AND(("post_id", post_id), ("status", CommentStatus::Approved)), tenant: tenant_id, order_by: "created_at ASC")?,
)
}
@@ -120,9 +120,9 @@ pub async fn find_approved_by_post_paginated(
) -> AppResult<(Vec<Comment>, i64)> {
let result = raisfast_derive::crud_query_paged!(
pool, Comment,
data_sql: "SELECT * FROM comments WHERE post_id = ? AND status = ?{tenant} ORDER BY created_at ASC",
count_sql: "SELECT COUNT(*) FROM comments WHERE post_id = ? AND status = ?{tenant}",
binds: [post_id, CommentStatus::Approved],
table: "comments",
where: AND(("post_id", post_id), ("status", CommentStatus::Approved)),
order_by: "created_at ASC",
tenant: tenant_id,
page: page,
page_size: page_size
@@ -135,7 +135,7 @@ pub async fn find_all_by_post(
post_id: SnowflakeId,
tenant_id: Option<&str>,
) -> AppResult<Vec<Comment>> {
raisfast_derive::crud_find_all!(pool, "comments", Comment, "post_id" => post_id, tenant: tenant_id, order_by: "created_at ASC").map_err(Into::into)
raisfast_derive::crud_find_all!(pool, "comments", Comment, where: ("post_id", post_id), tenant: tenant_id, order_by: "created_at ASC").map_err(Into::into)
}
#[cfg_attr(feature = "export-types", derive(TS))]
@@ -185,16 +185,17 @@ pub async fn find_all_paginated(
page_size: i64,
tenant_id: Option<&str>,
) -> AppResult<(Vec<AdminCommentRow>, i64)> {
let result = raisfast_derive::crud_query_paged!(
let (rows, total) = raisfast_derive::crud_join_paged!(
pool, AdminCommentRowDb,
data_sql: "SELECT c.id, c.post_id, p.title AS post_title, c.created_by, c.nickname, c.email, c.content, c.parent_id, c.status, c.created_at FROM comments c JOIN posts p ON c.post_id = p.id WHERE 1=1{tenant} ORDER BY c.created_at DESC",
count_sql: "SELECT COUNT(*) FROM comments WHERE 1=1{tenant}",
binds: [],
select: ["c.id", "c.post_id", "p.title AS post_title", "c.created_by", "c.nickname", "c.email", "c.content", "c.parent_id", "c.status", "c.created_at"],
from: "comments c",
joins: [INNER "posts p" ON "c.post_id = p.id"],
tenant_alias: "c",
tenant: tenant_id,
order_by: "c.created_at DESC",
page: page,
page_size: page_size
);
let (rows, total) = result;
Ok((rows.into_iter().map(AdminCommentRow::from).collect(), total))
}
@@ -207,7 +208,7 @@ pub async fn update_status(
let now = crate::utils::tz::now_utc();
let result = raisfast_derive::crud_update!(pool, "comments",
bind: ["status" => status, "updated_at" => &now],
where: "id" => id,
where: ("id", id),
tenant: tenant_id
)?;
@@ -219,7 +220,7 @@ pub async fn delete(
id: SnowflakeId,
tenant_id: Option<&str>,
) -> AppResult<()> {
let result = raisfast_derive::crud_delete!(pool, "comments", "id" => id, tenant: tenant_id)?;
let result = raisfast_derive::crud_delete!(pool, "comments", where: ("id", id), tenant: tenant_id)?;
AppError::expect_affected(&result, "comment")
}
+4 -4
View File
@@ -63,7 +63,7 @@ pub async fn create_revision(
"created_at" => now,
])?;
Ok(raisfast_derive::crud_find_one!(pool, "content_revisions", ContentRevision, "id" => id)?)
Ok(raisfast_derive::crud_find_one!(pool, "content_revisions", ContentRevision, where: ("id", id))?)
}
async fn next_revision_number(
@@ -97,7 +97,7 @@ pub async fn list_revisions(
record_id: SnowflakeId,
) -> AppResult<Vec<ContentRevision>> {
Ok(
raisfast_derive::crud_find_all!(pool, "content_revisions", ContentRevision, "content_type" => content_type, and: ["record_id" => record_id], order_by: "revision_number DESC")?,
raisfast_derive::crud_find_all!(pool, "content_revisions", ContentRevision, where: AND(("content_type", content_type), ("record_id", record_id)), order_by: "revision_number DESC")?,
)
}
@@ -108,7 +108,7 @@ pub async fn get_revision(
revision_id: SnowflakeId,
) -> AppResult<Option<ContentRevision>> {
Ok(
raisfast_derive::crud_find!(pool, "content_revisions", ContentRevision, "id" => revision_id, and: ["content_type" => content_type, "record_id" => record_id])?,
raisfast_derive::crud_find!(pool, "content_revisions", ContentRevision, where: AND(("id", revision_id), ("content_type", content_type), ("record_id", record_id)))?,
)
}
@@ -158,7 +158,7 @@ pub async fn delete_revisions(
content_type: &str,
record_id: SnowflakeId,
) -> AppResult<u64> {
let result = raisfast_derive::crud_delete!(pool, "content_revisions", "content_type" => content_type, and: ["record_id" => record_id])?;
let result = raisfast_derive::crud_delete!(pool, "content_revisions", where: AND(("content_type", content_type), ("record_id", record_id)))?;
Ok(result.rows_affected())
}
+7 -8
View File
@@ -18,14 +18,14 @@ pub struct Currency {
}
pub async fn find_by_code(pool: &crate::db::Pool, code: &str) -> AppResult<Option<Currency>> {
raisfast_derive::crud_find!(pool, "currencies", Currency, "code" => code).map_err(Into::into)
raisfast_derive::crud_find!(pool, "currencies", Currency, where: ("code", code)).map_err(Into::into)
}
pub async fn find_active_by_code(
pool: &crate::db::Pool,
code: &str,
) -> AppResult<Option<Currency>> {
raisfast_derive::crud_find!(pool, "currencies", Currency, "code" => code, and: ["is_active" => 1i64])
raisfast_derive::crud_find!(pool, "currencies", Currency, where: AND(("code", code), ("is_active", 1i64)))
.map_err(Into::into)
}
@@ -33,7 +33,7 @@ pub async fn find_by_code_tx(
tx: &mut crate::db::pool::DbConnection,
code: &str,
) -> AppResult<Option<Currency>> {
raisfast_derive::crud_find!(tx, "currencies", Currency, "code" => code, and: ["is_active" => 1i64])
raisfast_derive::crud_find!(tx, "currencies", Currency, where: AND(("code", code), ("is_active", 1i64)))
.map_err(Into::into)
}
@@ -75,7 +75,7 @@ pub async fn create(
]
)?;
raisfast_derive::crud_find_one!(pool, "currencies", Currency, "id" => id).map_err(Into::into)
raisfast_derive::crud_find_one!(pool, "currencies", Currency, where: ("id", id)).map_err(Into::into)
}
pub async fn update(
@@ -106,8 +106,7 @@ pub async fn update(
let result = raisfast_derive::crud_update!(pool, "currencies",
bind: ["name" => name, "is_active" => is_active, "updated_at" => now],
raw: ["version" => "version + 1"],
where: "id" => existing.id,
and: ["version" => existing.version]
where: AND(("id", existing.id), ("version", existing.version))
)?;
let affected = result.rows_affected();
@@ -127,7 +126,7 @@ pub async fn delete_by_code(pool: &crate::db::Pool, code: &str) -> AppResult<boo
None => return Ok(false),
};
let count: i64 = raisfast_derive::crud_count!(pool, "wallets", "currency" => code)?;
let count: i64 = raisfast_derive::crud_count!(pool, "wallets", where: ("currency", code))?;
if count > 0 {
return Err(crate::errors::app_error::AppError::BadRequest(format!(
@@ -135,7 +134,7 @@ pub async fn delete_by_code(pool: &crate::db::Pool, code: &str) -> AppResult<boo
)));
}
let result = raisfast_derive::crud_delete!(pool, "currencies", "id" => existing.id)?;
let result = raisfast_derive::crud_delete!(pool, "currencies", where: ("id", existing.id))?;
Ok(result.rows_affected() > 0)
}
+3 -5
View File
@@ -68,8 +68,7 @@ pub async fn find_by_token(
pool,
"email_verification_tokens",
EmailVerificationToken,
"token" => token,
and_null: ["verified_at"]
where: AND(("token", token), ("verified_at", IS_NULL))
)?)
}
@@ -78,7 +77,7 @@ pub async fn mark_verified(pool: &crate::db::Pool, id: SnowflakeId) -> AppResult
let now = crate::utils::tz::now_utc();
raisfast_derive::crud_update!(pool, "email_verification_tokens",
bind: ["verified_at" => now],
where: "id" => id
where: ("id", id)
)?;
Ok(())
}
@@ -88,8 +87,7 @@ pub async fn delete_unused_by_user(pool: &crate::db::Pool, user_id: SnowflakeId)
raisfast_derive::crud_delete!(
pool,
"email_verification_tokens",
"user_id" => user_id,
and_null: ["verified_at"]
where: AND(("user_id", user_id), ("verified_at", IS_NULL))
)?;
Ok(())
}
+8 -9
View File
@@ -61,7 +61,7 @@ pub async fn create(
)?;
let media =
raisfast_derive::crud_find_one!(pool, "media", Media, "id" => id, tenant: tenant_id)?;
raisfast_derive::crud_find_one!(pool, "media", Media, where: ("id", id), tenant: tenant_id)?;
Ok(media)
}
@@ -75,9 +75,9 @@ pub async fn find_all(
) -> AppResult<(Vec<Media>, i64)> {
let result = raisfast_derive::crud_query_paged!(
pool, Media,
data_sql: "SELECT * FROM media WHERE user_id = ?{tenant} ORDER BY created_at DESC",
count_sql: "SELECT COUNT(*) FROM media WHERE user_id = ?{tenant}",
binds: [user_id],
table: "media",
where: ("user_id", user_id),
order_by: "created_at DESC",
tenant: tenant_id,
page: page,
page_size: page_size
@@ -93,9 +93,8 @@ pub async fn find_all_admin(
) -> AppResult<(Vec<Media>, i64)> {
let result = raisfast_derive::crud_query_paged!(
pool, Media,
data_sql: "SELECT * FROM media WHERE 1=1{tenant} ORDER BY created_at DESC",
count_sql: "SELECT COUNT(*) FROM media WHERE 1=1{tenant}",
binds: [],
table: "media",
order_by: "created_at DESC",
tenant: tenant_id,
page: page,
page_size: page_size
@@ -108,7 +107,7 @@ pub async fn find_by_id(
id: SnowflakeId,
tenant_id: Option<&str>,
) -> AppResult<Option<Media>> {
Ok(raisfast_derive::crud_find!(pool, "media", Media, "id" => id, tenant: tenant_id)?)
Ok(raisfast_derive::crud_find!(pool, "media", Media, where: ("id", id), tenant: tenant_id)?)
}
#[derive(Debug, Serialize, Clone)]
@@ -179,7 +178,7 @@ pub async fn delete(
id: SnowflakeId,
tenant_id: Option<&str>,
) -> AppResult<()> {
let result = raisfast_derive::crud_delete!(pool, "media", "id" => id, tenant: tenant_id)?;
let result = raisfast_derive::crud_delete!(pool, "media", where: ("id", id), tenant: tenant_id)?;
AppError::expect_affected(&result, "media")
}
+7 -7
View File
@@ -79,7 +79,7 @@ pub async fn consume_state(
.await?;
if state.is_some() {
raisfast_derive::crud_delete!(pool, "oauth_states", "id" => id)?;
raisfast_derive::crud_delete!(pool, "oauth_states", where: ("id", id))?;
}
Ok(state)
@@ -101,7 +101,7 @@ pub async fn find_by_provider_user(
provider: &str,
provider_user_id: &str,
) -> AppResult<Option<OAuthAccount>> {
raisfast_derive::crud_find!(pool, "oauth_accounts", OAuthAccount, "provider" => provider, and: ["provider_user_id" => provider_user_id])
raisfast_derive::crud_find!(pool, "oauth_accounts", OAuthAccount, where: AND(("provider", provider), ("provider_user_id", provider_user_id)))
.map_err(Into::into)
}
@@ -111,7 +111,7 @@ pub async fn find_by_user_id(
user_id: SnowflakeId,
) -> AppResult<Vec<OAuthAccount>> {
raisfast_derive::check_schema!("oauth_accounts", "user_id", "created_at");
let accounts = raisfast_derive::crud_find_all!(pool, "oauth_accounts", OAuthAccount, "user_id" => user_id, order_by: "created_at")?;
let accounts = raisfast_derive::crud_find_all!(pool, "oauth_accounts", OAuthAccount, where: ("user_id", user_id), order_by: "created_at")?;
Ok(accounts)
}
@@ -155,7 +155,7 @@ pub async fn create_account(
"updated_at" => now,
])?;
Ok(raisfast_derive::crud_find_one!(pool, "oauth_accounts", OAuthAccount, "id" => id)?)
Ok(raisfast_derive::crud_find_one!(pool, "oauth_accounts", OAuthAccount, where: ("id", id))?)
}
/// Parameters for updating an OAuth account binding
@@ -187,7 +187,7 @@ pub async fn update_account(
"token_expires_at" => params.token_expires_at,
"profile" => params.profile,
],
where: "id" => params.id
where: ("id", params.id)
)?;
Ok(())
}
@@ -198,13 +198,13 @@ pub async fn delete_account(
user_id: SnowflakeId,
provider: &str,
) -> AppResult<bool> {
let result = raisfast_derive::crud_delete!(pool, "oauth_accounts", "user_id" => user_id, and: ["provider" => provider])?;
let result = raisfast_derive::crud_delete!(pool, "oauth_accounts", where: AND(("user_id", user_id), ("provider", provider)))?;
Ok(result.rows_affected() > 0)
}
/// Count the number of OAuth providers bound to a user
pub async fn count_by_user(pool: &crate::db::Pool, user_id: SnowflakeId) -> AppResult<i64> {
Ok(raisfast_derive::crud_count!(pool, "oauth_accounts", "user_id" => user_id)?)
Ok(raisfast_derive::crud_count!(pool, "oauth_accounts", where: ("user_id", user_id))?)
}
#[cfg(test)]
+4 -4
View File
@@ -42,7 +42,7 @@ pub struct OptionRow {
/// Query all autoload options (preloaded at startup)
pub async fn find_autoload(pool: &crate::db::Pool) -> AppResult<Vec<OptionRow>> {
Ok(raisfast_derive::crud_find_all!(pool, "options", OptionRow, "autoload" => 1_i64)?)
Ok(raisfast_derive::crud_find_all!(pool, "options", OptionRow, where: ("autoload", 1_i64))?)
}
/// Query a single option by key
@@ -52,7 +52,7 @@ pub async fn find_by_key(
tenant_id: Option<&str>,
) -> AppResult<Option<OptionRow>> {
Ok(
raisfast_derive::crud_find!(pool, "options", OptionRow, "option_key" => key, tenant: tenant_id)?,
raisfast_derive::crud_find!(pool, "options", OptionRow, where: ("option_key", key), tenant: tenant_id)?,
)
}
@@ -76,7 +76,7 @@ pub async fn upsert_value(
let now = crate::utils::tz::now_utc();
raisfast_derive::crud_update!(pool, "options",
bind: ["value" => value, "updated_at" => now],
where: "option_key" => key,
where: ("option_key", key),
tenant: tenant_id
)?;
Ok(())
@@ -88,7 +88,7 @@ pub async fn delete_by_key(
key: &str,
tenant_id: Option<&str>,
) -> AppResult<()> {
raisfast_derive::crud_delete!(pool, "options", "option_key" => key, tenant: tenant_id)?;
raisfast_derive::crud_delete!(pool, "options", where: ("option_key", key), tenant: tenant_id)?;
Ok(())
}
+11 -13
View File
@@ -62,7 +62,7 @@ pub async fn find_by_id(
id: SnowflakeId,
tenant_id: Option<&str>,
) -> AppResult<Option<Order>> {
raisfast_derive::crud_find!(pool, "orders", Order, "id" => id, tenant: tenant_id)
raisfast_derive::crud_find!(pool, "orders", Order, where: ("id", id), tenant: tenant_id)
.map_err(Into::into)
}
@@ -71,7 +71,7 @@ pub async fn find_by_order_no(
order_no: &str,
tenant_id: Option<&str>,
) -> AppResult<Option<Order>> {
raisfast_derive::crud_find!(pool, "orders", Order, "order_no" => order_no, tenant: tenant_id)
raisfast_derive::crud_find!(pool, "orders", Order, where: ("order_no", order_no), tenant: tenant_id)
.map_err(Into::into)
}
@@ -84,9 +84,9 @@ pub async fn find_by_user_paginated(
) -> AppResult<(Vec<Order>, i64)> {
let result = raisfast_derive::crud_query_paged!(
pool, Order,
data_sql: "SELECT * FROM orders WHERE user_id = ?{tenant} ORDER BY created_at DESC",
count_sql: "SELECT COUNT(*) FROM orders WHERE user_id = ?{tenant}",
binds: [user_id],
table: "orders",
where: ("user_id", user_id),
order_by: "created_at DESC",
tenant: tenant_id,
page: page,
page_size: page_size
@@ -103,10 +103,9 @@ pub async fn find_all_admin_paginated(
) -> AppResult<(Vec<Order>, i64)> {
let result = raisfast_derive::crud_query_paged!(
pool, Order,
data_sql: "SELECT * FROM orders WHERE 1=1{tenant} ORDER BY created_at DESC",
count_sql: "SELECT COUNT(*) FROM orders WHERE 1=1{tenant}",
binds: [],
table: "orders",
where: ["status" => status],
order_by: "created_at DESC",
tenant: tenant_id,
page: page,
page_size: page_size
@@ -225,7 +224,7 @@ pub async fn update_shipped(
"carrier" => carrier,
],
raw: ["updated_at" => crate::db::Driver::now_fn()],
where: "id" => id,
where: ("id", id),
tenant: tenant_id
)?;
Ok(())
@@ -241,7 +240,7 @@ pub async fn update_admin_remark(
pool, "orders",
bind: ["admin_remark" => admin_remark],
raw: ["updated_at" => crate::db::Driver::now_fn()],
where: "id" => id,
where: ("id", id),
tenant: tenant_id
)?;
Ok(())
@@ -257,7 +256,7 @@ pub async fn update_delivery_data(
pool, "orders",
bind: ["delivery_data" => delivery_data],
raw: ["updated_at" => crate::db::Driver::now_fn()],
where: "id" => id,
where: ("id", id),
tenant: tenant_id
)?;
Ok(())
@@ -361,8 +360,7 @@ pub async fn tx_update_shipped(
"carrier" => carrier
],
raw: ["updated_at" => crate::db::Driver::now_fn()],
where: "id" => id,
and: ["status" => OrderStatus::Paid.as_str()]
where: AND(("id", id), ("status", OrderStatus::Paid.as_str()))
)?;
Ok(result.rows_affected())
}
+3 -3
View File
@@ -31,7 +31,7 @@ pub async fn find_by_order_id(
order_id: SnowflakeId,
tenant_id: Option<&str>,
) -> AppResult<Vec<OrderItem>> {
raisfast_derive::crud_find_all!(pool, "order_items", OrderItem, "order_id" => order_id, tenant: tenant_id)
raisfast_derive::crud_find_all!(pool, "order_items", OrderItem, where: ("order_id", order_id), tenant: tenant_id)
.map_err(Into::into)
}
@@ -65,7 +65,7 @@ pub async fn insert(
],
tenant: tenant_id
)?;
raisfast_derive::crud_find_one!(pool, "order_items", OrderItem, "id" => id, tenant: tenant_id)
raisfast_derive::crud_find_one!(pool, "order_items", OrderItem, where: ("id", id), tenant: tenant_id)
.map_err(Into::into)
}
@@ -110,7 +110,7 @@ pub async fn tx_insert(
],
tenant: tenant_id
)?;
raisfast_derive::crud_find_one!(&mut *tx, "order_items", OrderItem, "id" => id, tenant: tenant_id)
raisfast_derive::crud_find_one!(&mut *tx, "order_items", OrderItem, where: ("id", id), tenant: tenant_id)
.map_err(Into::into)
}
+9 -10
View File
@@ -315,7 +315,7 @@ pub async fn find_by_slug(
slug: &str,
tenant_id: Option<&str>,
) -> AppResult<Option<Page>> {
Ok(raisfast_derive::crud_find!(pool, "pages", Page, "slug" => slug, tenant: tenant_id)?)
Ok(raisfast_derive::crud_find!(pool, "pages", Page, where: ("slug", slug), tenant: tenant_id)?)
}
pub async fn find_by_id(
@@ -323,7 +323,7 @@ pub async fn find_by_id(
id: SnowflakeId,
tenant_id: Option<&str>,
) -> AppResult<Option<Page>> {
Ok(raisfast_derive::crud_find!(pool, "pages", Page, "id" => id, tenant: tenant_id)?)
Ok(raisfast_derive::crud_find!(pool, "pages", Page, where: ("id", id), tenant: tenant_id)?)
}
pub async fn list_published(
@@ -334,9 +334,9 @@ pub async fn list_published(
) -> AppResult<(Vec<Page>, i64)> {
let result = raisfast_derive::crud_query_paged!(
pool, Page,
data_sql: "SELECT * FROM pages WHERE status = ?{tenant} ORDER BY sort_order ASC, created_at DESC",
count_sql: "SELECT COUNT(*) FROM pages WHERE status = ?{tenant}",
binds: [PageStatus::Published],
table: "pages",
where: ("status", PageStatus::Published),
order_by: "sort_order ASC, created_at DESC",
tenant: tenant_id,
page: page,
page_size: page_size
@@ -353,10 +353,9 @@ pub async fn list_all(
) -> AppResult<(Vec<Page>, i64)> {
let result = raisfast_derive::crud_query_paged!(
pool, Page,
data_sql: "SELECT * FROM pages WHERE 1=1{tenant} ORDER BY sort_order ASC, created_at DESC",
count_sql: "SELECT COUNT(*) FROM pages WHERE 1=1{tenant}",
binds: [],
table: "pages",
where: ["status" => status],
order_by: "sort_order ASC, created_at DESC",
tenant: tenant_id,
page: page,
page_size: page_size
@@ -570,7 +569,7 @@ pub async fn delete(
id: SnowflakeId,
tenant_id: Option<&str>,
) -> AppResult<()> {
let result = raisfast_derive::crud_delete!(pool, "pages", "id" => id, tenant: tenant_id)?;
let result = raisfast_derive::crud_delete!(pool, "pages", where: ("id", id), tenant: tenant_id)?;
AppError::expect_affected(&result, "page")
}
@@ -651,7 +650,7 @@ pub async fn reorder(
raisfast_derive::crud_update!(
pool, "pages",
bind: ["sort_order" => sort_order, "updated_at" => now],
where: "id" => id,
where: ("id", id),
tenant: tenant_id
)?;
}
+3 -5
View File
@@ -69,8 +69,7 @@ pub async fn find_by_token(
pool,
"password_reset_tokens",
PasswordResetToken,
"token" => token,
and_null: ["used_at"]
where: AND(("token", token), ("used_at", IS_NULL))
)?)
}
@@ -79,7 +78,7 @@ pub async fn mark_used(pool: &crate::db::Pool, id: SnowflakeId) -> AppResult<()>
let now = crate::utils::tz::now_utc();
raisfast_derive::crud_update!(pool, "password_reset_tokens",
bind: ["used_at" => now],
where: "id" => id
where: ("id", id)
)?;
Ok(())
}
@@ -89,8 +88,7 @@ pub async fn delete_unused_by_user(pool: &crate::db::Pool, user_id: SnowflakeId)
raisfast_derive::crud_delete!(
pool,
"password_reset_tokens",
"user_id" => user_id,
and_null: ["used_at"]
where: AND(("user_id", user_id), ("used_at", IS_NULL))
)?;
Ok(())
}
+6 -8
View File
@@ -27,7 +27,7 @@ pub async fn find_by_id(
id: SnowflakeId,
tenant_id: Option<&str>,
) -> AppResult<Option<PaymentChannel>> {
raisfast_derive::crud_find!(pool, "payment_channels", PaymentChannel, "id" => id, tenant: tenant_id)
raisfast_derive::crud_find!(pool, "payment_channels", PaymentChannel, where: ("id", id), tenant: tenant_id)
.map_err(Into::into)
}
@@ -35,7 +35,7 @@ pub async fn find_all_active(
pool: &crate::db::Pool,
tenant_id: Option<&str>,
) -> AppResult<Vec<PaymentChannel>> {
raisfast_derive::crud_find_all!(pool, "payment_channels", PaymentChannel, "is_active" => 1_i64, tenant: tenant_id, order_by: "sort_order, created_at DESC")
raisfast_derive::crud_find_all!(pool, "payment_channels", PaymentChannel, where: ("is_active", 1_i64), tenant: tenant_id, order_by: "sort_order, created_at DESC")
.map_err(Into::into)
}
@@ -49,10 +49,9 @@ pub async fn find_all_admin_paginated(
let active_val = is_active.map(|a| if a { 1_i64 } else { 0_i64 });
let result = raisfast_derive::crud_query_paged!(
pool, PaymentChannel,
data_sql: "SELECT * FROM payment_channels WHERE 1=1{tenant} ORDER BY sort_order, created_at DESC",
count_sql: "SELECT COUNT(*) FROM payment_channels WHERE 1=1{tenant}",
binds: [],
table: "payment_channels",
where: ["is_active" => active_val],
order_by: "sort_order, created_at DESC",
tenant: tenant_id,
page: page,
page_size: page_size
@@ -116,8 +115,7 @@ pub async fn update(
"sort_order" => cmd.sort_order,
],
raw: ["updated_at" => crate::db::Driver::now_fn(), "version" => "version + 1"],
where: "id" => cmd.id,
and: ["version" => cmd.version],
where: AND(("id", cmd.id), ("version", cmd.version)),
tenant: tenant_id
)?
.rows_affected();
@@ -130,7 +128,7 @@ pub async fn delete_by_id(
tenant_id: Option<&str>,
) -> AppResult<bool> {
let affected =
raisfast_derive::crud_delete!(pool, "payment_channels", "id" => id, tenant: tenant_id)?
raisfast_derive::crud_delete!(pool, "payment_channels", where: ("id", id), tenant: tenant_id)?
.rows_affected();
Ok(affected > 0)
}
+9 -10
View File
@@ -72,7 +72,7 @@ pub async fn find_by_id(
id: SnowflakeId,
tenant_id: Option<&str>,
) -> AppResult<Option<PaymentOrder>> {
raisfast_derive::crud_find!(pool, "payment_orders", PaymentOrder, "id" => id, tenant: tenant_id)
raisfast_derive::crud_find!(pool, "payment_orders", PaymentOrder, where: ("id", id), tenant: tenant_id)
.map_err(Into::into)
}
@@ -81,7 +81,7 @@ pub async fn find_by_idempotency_key(
key: &str,
tenant_id: Option<&str>,
) -> AppResult<Option<PaymentOrder>> {
raisfast_derive::crud_find!(pool, "payment_orders", PaymentOrder, "idempotency_key" => key, tenant: tenant_id)
raisfast_derive::crud_find!(pool, "payment_orders", PaymentOrder, where: ("idempotency_key", key), tenant: tenant_id)
.map_err(Into::into)
}
@@ -90,7 +90,7 @@ pub async fn find_by_provider_order_id(
provider_order_id: &str,
tenant_id: Option<&str>,
) -> AppResult<Option<PaymentOrder>> {
raisfast_derive::crud_find!(pool, "payment_orders", PaymentOrder, "provider_order_id" => provider_order_id, tenant: tenant_id).map_err(Into::into)
raisfast_derive::crud_find!(pool, "payment_orders", PaymentOrder, where: ("provider_order_id", provider_order_id), tenant: tenant_id).map_err(Into::into)
}
pub async fn find_by_user_paginated(
@@ -102,9 +102,9 @@ pub async fn find_by_user_paginated(
) -> AppResult<(Vec<PaymentOrder>, i64)> {
let result = raisfast_derive::crud_query_paged!(
pool, PaymentOrder,
data_sql: "SELECT * FROM payment_orders WHERE user_id = ?{tenant} ORDER BY created_at DESC",
count_sql: "SELECT COUNT(*) FROM payment_orders WHERE user_id = ?{tenant}",
binds: [user_id],
table: "payment_orders",
where: ("user_id", user_id),
order_by: "created_at DESC",
tenant: tenant_id,
page: page,
page_size: page_size
@@ -121,10 +121,9 @@ pub async fn find_all_admin_paginated(
) -> AppResult<(Vec<PaymentOrder>, i64)> {
let result = raisfast_derive::crud_query_paged!(
pool, PaymentOrder,
data_sql: "SELECT * FROM payment_orders WHERE 1=1{tenant} ORDER BY created_at DESC",
count_sql: "SELECT COUNT(*) FROM payment_orders WHERE 1=1{tenant}",
binds: [],
table: "payment_orders",
where: ["status" => status],
order_by: "created_at DESC",
tenant: tenant_id,
page: page,
page_size: page_size
@@ -186,7 +185,7 @@ pub async fn update_provider_order_id(
pool, "payment_orders",
bind: ["provider_order_id" => provider_order_id, "provider_data" => provider_data],
raw: ["updated_at" => crate::db::Driver::now_fn()],
where: "id" => id,
where: ("id", id),
tenant: tenant_id
)?;
Ok(())
+7 -8
View File
@@ -29,7 +29,7 @@ pub async fn find_by_id(
id: SnowflakeId,
tenant_id: Option<&str>,
) -> AppResult<Option<PaymentRefund>> {
raisfast_derive::crud_find!(pool, "payment_refunds", PaymentRefund, "id" => id, tenant: tenant_id)
raisfast_derive::crud_find!(pool, "payment_refunds", PaymentRefund, where: ("id", id), tenant: tenant_id)
.map_err(Into::into)
}
@@ -38,7 +38,7 @@ pub async fn find_by_payment_order_id(
payment_order_id: SnowflakeId,
tenant_id: Option<&str>,
) -> AppResult<Vec<PaymentRefund>> {
raisfast_derive::crud_find_all!(pool, "payment_refunds", PaymentRefund, "payment_order_id" => payment_order_id, tenant: tenant_id, order_by: "created_at DESC")
raisfast_derive::crud_find_all!(pool, "payment_refunds", PaymentRefund, where: ("payment_order_id", payment_order_id), tenant: tenant_id, order_by: "created_at DESC")
.map_err(Into::into)
}
@@ -47,7 +47,7 @@ pub async fn find_by_order_id(
order_id: &str,
tenant_id: Option<&str>,
) -> AppResult<Vec<PaymentRefund>> {
raisfast_derive::crud_find_all!(pool, "payment_refunds", PaymentRefund, "order_id" => order_id, tenant: tenant_id, order_by: "created_at DESC")
raisfast_derive::crud_find_all!(pool, "payment_refunds", PaymentRefund, where: ("order_id", order_id), tenant: tenant_id, order_by: "created_at DESC")
.map_err(Into::into)
}
@@ -78,7 +78,7 @@ pub async fn insert(
],
tenant: tenant_id
)?;
raisfast_derive::crud_find_one!(pool, "payment_refunds", PaymentRefund, "id" => id, tenant: tenant_id).map_err(Into::into)
raisfast_derive::crud_find_one!(pool, "payment_refunds", PaymentRefund, where: ("id", id), tenant: tenant_id).map_err(Into::into)
}
pub async fn update_status(
@@ -91,7 +91,7 @@ pub async fn update_status(
pool, "payment_refunds",
bind: ["status" => status],
raw: ["updated_at" => crate::db::Driver::now_fn()],
where: "id" => id,
where: ("id", id),
tenant: tenant_id
)?;
Ok(())
@@ -126,9 +126,8 @@ pub async fn find_all_admin_paginated(
) -> AppResult<(Vec<PaymentRefund>, i64)> {
let result = raisfast_derive::crud_query_paged!(
pool, PaymentRefund,
data_sql: "SELECT * FROM payment_refunds WHERE 1=1{tenant} ORDER BY created_at DESC",
count_sql: "SELECT COUNT(*) FROM payment_refunds WHERE 1=1{tenant}",
binds: [],
table: "payment_refunds",
order_by: "created_at DESC",
tenant: tenant_id,
page: page,
page_size: page_size
+6 -7
View File
@@ -25,7 +25,7 @@ pub async fn find_by_id(
id: SnowflakeId,
tenant_id: Option<&str>,
) -> AppResult<Option<PaymentTransaction>> {
raisfast_derive::crud_find!(pool, "payment_transactions", PaymentTransaction, "id" => id, tenant: tenant_id)
raisfast_derive::crud_find!(pool, "payment_transactions", PaymentTransaction, where: ("id", id), tenant: tenant_id)
.map_err(Into::into)
}
@@ -34,7 +34,7 @@ pub async fn find_by_payment_order_id(
payment_order_id: SnowflakeId,
tenant_id: Option<&str>,
) -> AppResult<Vec<PaymentTransaction>> {
raisfast_derive::crud_find_all!(pool, "payment_transactions", PaymentTransaction, "payment_order_id" => payment_order_id, tenant: tenant_id, order_by: "created_at DESC")
raisfast_derive::crud_find_all!(pool, "payment_transactions", PaymentTransaction, where: ("payment_order_id", payment_order_id), tenant: tenant_id, order_by: "created_at DESC")
.map_err(Into::into)
}
@@ -43,7 +43,7 @@ pub async fn find_by_order_id(
order_id: &str,
tenant_id: Option<&str>,
) -> AppResult<Vec<PaymentTransaction>> {
raisfast_derive::crud_find_all!(pool, "payment_transactions", PaymentTransaction, "order_id" => order_id, tenant: tenant_id, order_by: "created_at DESC")
raisfast_derive::crud_find_all!(pool, "payment_transactions", PaymentTransaction, where: ("order_id", order_id), tenant: tenant_id, order_by: "created_at DESC")
.map_err(Into::into)
}
@@ -52,7 +52,7 @@ pub async fn find_by_provider_tx_id(
provider_tx_id: &str,
tenant_id: Option<&str>,
) -> AppResult<Option<PaymentTransaction>> {
raisfast_derive::crud_find!(pool, "payment_transactions", PaymentTransaction, "provider_tx_id" => provider_tx_id, tenant: tenant_id)
raisfast_derive::crud_find!(pool, "payment_transactions", PaymentTransaction, where: ("provider_tx_id", provider_tx_id), tenant: tenant_id)
.map_err(Into::into)
}
@@ -64,9 +64,8 @@ pub async fn find_all_admin_paginated(
) -> AppResult<(Vec<PaymentTransaction>, i64)> {
let result = raisfast_derive::crud_query_paged!(
pool, PaymentTransaction,
data_sql: "SELECT * FROM payment_transactions WHERE 1=1{tenant} ORDER BY created_at DESC",
count_sql: "SELECT COUNT(*) FROM payment_transactions WHERE 1=1{tenant}",
binds: [],
table: "payment_transactions",
order_by: "created_at DESC",
tenant: tenant_id,
page: page,
page_size: page_size
+3 -5
View File
@@ -38,8 +38,7 @@ pub async fn get(pool: &Pool, plugin_id: &str, key: &str) -> AppResult<Option<St
let now = crate::utils::tz::now_utc();
if exp < &now {
let _ = raisfast_derive::crud_delete!(
pool, "plugin_storage", "plugin_id" => plugin_id,
and: ["storage_key" => key]
pool, "plugin_storage", where: AND(("plugin_id", plugin_id), ("storage_key", key))
);
return Ok(None);
}
@@ -80,15 +79,14 @@ pub async fn delete(pool: &Pool, plugin_id: &str, key: &str) -> AppResult<()> {
raisfast_derive::crud_delete!(
pool,
"plugin_storage",
"plugin_id" => plugin_id,
and: ["storage_key" => key]
where: AND(("plugin_id", plugin_id), ("storage_key", key))
)?;
Ok(())
}
/// Delete all data for a plugin
pub async fn delete_all(pool: &Pool, plugin_id: &str) -> AppResult<()> {
raisfast_derive::crud_delete!(pool, "plugin_storage", "plugin_id" => plugin_id)?;
raisfast_derive::crud_delete!(pool, "plugin_storage", where: ("plugin_id", plugin_id))?;
Ok(())
}
+18 -22
View File
@@ -78,7 +78,7 @@ pub async fn find_by_slug(
slug: &str,
tenant_id: Option<&str>,
) -> AppResult<Option<Post>> {
let post = raisfast_derive::crud_find!(pool, "posts", Post, "slug" => slug, tenant: tenant_id)?;
let post = raisfast_derive::crud_find!(pool, "posts", Post, where: ("slug", slug), tenant: tenant_id)?;
Ok(post)
}
@@ -87,7 +87,7 @@ pub async fn find_by_id(
id: SnowflakeId,
tenant_id: Option<&str>,
) -> AppResult<Option<Post>> {
let post = raisfast_derive::crud_find!(pool, "posts", Post, "id" => id, tenant: tenant_id)?;
let post = raisfast_derive::crud_find!(pool, "posts", Post, where: ("id", id), tenant: tenant_id)?;
Ok(post)
}
@@ -152,7 +152,7 @@ async fn find_by_id_tx(
id: SnowflakeId,
tenant_id: Option<&str>,
) -> AppResult<Option<Post>> {
raisfast_derive::crud_find!(&mut **tx, "posts", Post, "id" => id, tenant: tenant_id)
raisfast_derive::crud_find!(&mut **tx, "posts", Post, where: ("id", id), tenant: tenant_id)
.map_err(Into::into)
}
@@ -174,7 +174,7 @@ pub async fn update_tx(
) -> AppResult<Post> {
let post_id = cmd.id;
let existing =
raisfast_derive::crud_find_one!(&mut **tx, "posts", Post, "id" => post_id, tenant: tenant_id)
raisfast_derive::crud_find_one!(&mut **tx, "posts", Post, where: ("id", post_id), tenant: tenant_id)
.map_err(|_| AppError::not_found("post"))?;
let now = crate::utils::tz::now_utc();
@@ -213,7 +213,7 @@ pub async fn update_tx(
"published_at" => published_at, "updated_by" => updated_by,
"updated_at" => now
],
where: "id" => post_id,
where: ("id", post_id),
tenant: tenant_id
)?;
@@ -253,7 +253,7 @@ pub async fn delete(
id: SnowflakeId,
tenant_id: Option<&str>,
) -> AppResult<()> {
let result = raisfast_derive::crud_delete!(pool, "posts", "id" => id, tenant: tenant_id)?;
let result = raisfast_derive::crud_delete!(pool, "posts", where: ("id", id), tenant: tenant_id)?;
AppError::expect_affected(&result, "post")
}
@@ -265,8 +265,7 @@ pub async fn increment_view_count_joined(
raisfast_derive::crud_update!(
pool, "posts",
raw: ["view_count" => "view_count + 1"],
where: "slug" => slug,
and: ["status" => PostStatus::Published],
where: AND(("slug", slug), ("status", PostStatus::Published)),
tenant: tenant_id
)?;
@@ -289,7 +288,7 @@ pub async fn sync_tags_tx(
post_id: SnowflakeId,
tag_ids: &[i64],
) -> AppResult<()> {
raisfast_derive::crud_delete!(&mut **tx, "posts_tags", "post_id" => post_id)?;
raisfast_derive::crud_delete!(&mut **tx, "posts_tags", where: ("post_id", post_id))?;
for tag_id in tag_ids {
raisfast_derive::crud_insert!(&mut **tx, "posts_tags", ["post_id" => post_id, "tag_id" => *tag_id])?;
@@ -315,7 +314,7 @@ pub async fn get_post_tags(
select: ["t.id", "t.name", "t.slug"],
from: "tags t",
joins: [INNER "posts_tags pt" ON "t.id = pt.tag_id"],
where: "pt.post_id" => post_id,
where: ("pt.post_id", post_id),
tenant_alias: "t",
tenant: tenant_id,
method: fetch_all
@@ -337,7 +336,7 @@ pub async fn get_author_name(
tenant_id: Option<&str>,
) -> AppResult<Option<String>> {
let row: Option<(String,)> = raisfast_derive::crud_select!(
pool, "users", ["username"], "id" => created_by, tenant_id
pool, "users", ["username"], where: ("id", created_by), tenant: tenant_id
)?;
Ok(row.map(|(s,)| s))
}
@@ -348,7 +347,7 @@ pub async fn get_category_name(
tenant_id: Option<&str>,
) -> AppResult<Option<String>> {
let row: Option<(String,)> = raisfast_derive::crud_select!(
pool, "categories", ["name"], "id" => category_id, tenant_id
pool, "categories", ["name"], where: ("id", category_id), tenant: tenant_id
)?;
Ok(row.map(|(s,)| s))
}
@@ -529,7 +528,7 @@ pub async fn find_all_joined(
LEFT "users u" ON "p.created_by = u.id",
LEFT "categories c" ON "p.category_id = c.id"
],
and: ["p.status" => s],
where: ("p.status", s),
tenant_alias: "p",
tenant: tenant_id,
order_by: "p.is_pinned DESC, p.created_at DESC",
@@ -636,7 +635,7 @@ pub async fn find_joined_by_id(
LEFT "users u" ON "p.created_by = u.id",
LEFT "categories c" ON "p.category_id = c.id"
],
where: "p.id" => id,
where: ("p.id", id),
tenant_alias: "p",
tenant: tenant_id,
method: fetch_one
@@ -656,8 +655,7 @@ pub async fn find_published_joined_by_slug(
LEFT "users u" ON "p.created_by = u.id",
LEFT "categories c" ON "p.category_id = c.id"
],
where: "p.slug" => slug,
and: ["p.status" => PostStatus::Published],
where: AND(("p.slug", slug), ("p.status", PostStatus::Published)),
tenant_alias: "p",
tenant: tenant_id,
method: fetch_one
@@ -687,7 +685,7 @@ pub async fn get_tags_for_posts(
select: ["pt.post_id", "t.id", "t.name", "t.slug"],
from: "posts_tags pt",
joins: [JOIN "tags" ON "pt.tag_id = t.id"],
and_in: ["pt.post_id" => post_ids],
where: ("pt.post_id", IN, post_ids),
tenant_alias: "t",
tenant: tenant_id,
method: fetch_all
@@ -732,8 +730,7 @@ pub async fn find_joined_by_ids(
LEFT "users u" ON "p.created_by = u.id",
LEFT "categories c" ON "p.category_id = c.id"
],
and: ["p.status" => PostStatus::Published],
and_in: ["p.id" => ids],
where: AND(("p.status", PostStatus::Published), ("p.id", IN, ids)),
tenant_alias: "p",
tenant: tenant_id,
order_by: "p.is_pinned DESC, p.created_at DESC",
@@ -754,9 +751,8 @@ pub async fn count_published_by_ids(
raisfast_derive::crud_count!(
pool,
"posts",
"status" => PostStatus::Published,
tenant: tenant_id,
and_in: ["id" => ids]
where: AND(("status", PostStatus::Published), ("id", IN, ids)),
tenant: tenant_id
)
.map_err(Into::into)
}
+8 -10
View File
@@ -83,7 +83,7 @@ pub async fn find_by_id(
id: SnowflakeId,
tenant_id: Option<&str>,
) -> AppResult<Option<Product>> {
raisfast_derive::crud_find!(pool, "products", Product, "id" => id, tenant: tenant_id)
raisfast_derive::crud_find!(pool, "products", Product, where: ("id", id), tenant: tenant_id)
.map_err(Into::into)
}
@@ -95,9 +95,9 @@ pub async fn find_active_paginated(
) -> AppResult<(Vec<Product>, i64)> {
let result = raisfast_derive::crud_query_paged!(
pool, Product,
data_sql: "SELECT * FROM products WHERE status = 'active'{tenant} ORDER BY sort_order, created_at DESC",
count_sql: "SELECT COUNT(*) FROM products WHERE status = 'active'{tenant}",
binds: [],
table: "products",
where: ("status", "active"),
order_by: "sort_order, created_at DESC",
tenant: tenant_id,
page: page,
page_size: page_size
@@ -114,10 +114,9 @@ pub async fn find_all_admin(
) -> AppResult<(Vec<Product>, i64)> {
let result = raisfast_derive::crud_query_paged!(
pool, Product,
data_sql: "SELECT * FROM products WHERE 1=1{tenant} ORDER BY sort_order, created_at DESC",
count_sql: "SELECT COUNT(*) FROM products WHERE 1=1{tenant}",
binds: [],
table: "products",
where: ["status" => status],
order_by: "sort_order, created_at DESC",
tenant: tenant_id,
page: page,
page_size: page_size
@@ -216,8 +215,7 @@ pub async fn update(
"has_variants" => cmd.has_variants,
],
raw: ["updated_at" => crate::db::Driver::now_fn(), "version" => "version + 1"],
where: "id" => cmd.id,
and: ["version" => cmd.version],
where: AND(("id", cmd.id), ("version", cmd.version)),
tenant: tenant_id
)?
.rows_affected();
@@ -229,7 +227,7 @@ pub async fn delete_by_id(
id: SnowflakeId,
tenant_id: Option<&str>,
) -> AppResult<bool> {
let result = raisfast_derive::crud_delete!(pool, "products", "id" => id, tenant: tenant_id)?;
let result = raisfast_derive::crud_delete!(pool, "products", where: ("id", id), tenant: tenant_id)?;
Ok(result.rows_affected() > 0)
}
+8 -9
View File
@@ -34,7 +34,7 @@ pub async fn find_by_id(
pool,
"product_variants",
ProductVariant,
"id" => id,
where: ("id", id),
tenant: tenant_id
)
.map_err(Into::into)
@@ -49,7 +49,7 @@ pub async fn find_by_sku(
pool,
"product_variants",
ProductVariant,
"sku" => sku,
where: ("sku", sku),
tenant: tenant_id
)
.map_err(Into::into)
@@ -64,7 +64,7 @@ pub async fn find_by_product_id(
pool,
"product_variants",
ProductVariant,
"product_id" => product_id,
where: ("product_id", product_id),
order_by: "sort_order, created_at",
tenant: tenant_id
)?)
@@ -79,8 +79,7 @@ pub async fn find_active_by_product_id(
pool,
"product_variants",
ProductVariant,
"product_id" => product_id,
and: ["is_active" => true],
where: AND(("product_id", product_id), ("is_active", true)),
order_by: "sort_order, created_at",
tenant: tenant_id
)?)
@@ -138,7 +137,7 @@ pub async fn update(
"is_active" => cmd.is_active,
],
raw: ["updated_at" => crate::db::Driver::now_fn()],
where: "id" => cmd.id,
where: ("id", cmd.id),
tenant: tenant_id
)?;
Ok(result.rows_affected() > 0)
@@ -150,7 +149,7 @@ pub async fn delete_by_id(
tenant_id: Option<&str>,
) -> AppResult<bool> {
let result: crate::db::DbQueryResult =
raisfast_derive::crud_delete!(pool, "product_variants", "id" => id, tenant: tenant_id)?;
raisfast_derive::crud_delete!(pool, "product_variants", where: ("id", id), tenant: tenant_id)?;
Ok(result.rows_affected() > 0)
}
@@ -162,7 +161,7 @@ pub async fn delete_by_product_id(
let result = raisfast_derive::crud_delete!(
pool,
"product_variants",
"product_id" => product_id,
where: ("product_id", product_id),
tenant: tenant_id
)?;
AppError::expect_affected(&result, "product_variant")
@@ -176,7 +175,7 @@ pub async fn count_by_product(
let count = raisfast_derive::crud_count!(
pool,
"product_variants",
"product_id" => product_id,
where: ("product_id", product_id),
tenant: tenant_id
)?;
Ok(count)
+5 -5
View File
@@ -55,7 +55,7 @@ pub async fn list_roles(pool: &crate::db::Pool) -> AppResult<Vec<Role>> {
/// Find role by id
pub async fn find_role_by_id(pool: &crate::db::Pool, id: SnowflakeId) -> AppResult<Option<Role>> {
let role = raisfast_derive::crud_find!(pool, "roles", Role, "id" => id)?;
let role = raisfast_derive::crud_find!(pool, "roles", Role, where: ("id", id))?;
Ok(role)
}
@@ -112,7 +112,7 @@ pub async fn update_role(
pool, "roles",
bind: ["updated_at" => now],
optional: ["name" => name, "description" => description],
where: "id" => id
where: ("id", id)
)?;
find_role_by_id(pool, id)
@@ -122,7 +122,7 @@ pub async fn update_role(
/// Delete role
pub async fn delete_role(pool: &crate::db::Pool, id: SnowflakeId) -> AppResult<()> {
raisfast_derive::crud_delete!(pool, "roles", "id" => id)?;
raisfast_derive::crud_delete!(pool, "roles", where: ("id", id))?;
Ok(())
}
@@ -141,7 +141,7 @@ pub async fn find_permissions_by_role_id(
"conditions",
"created_at"
);
let perms = raisfast_derive::crud_find_all!(pool, "permissions", Permission, "role_id" => role_id, order_by: "action")?;
let perms = raisfast_derive::crud_find_all!(pool, "permissions", Permission, where: ("role_id", role_id), order_by: "action")?;
Ok(perms)
}
@@ -150,7 +150,7 @@ pub async fn delete_permissions_by_role_id(
pool: &crate::db::Pool,
role_id: SnowflakeId,
) -> AppResult<()> {
raisfast_derive::crud_delete!(pool, "permissions", "role_id" => role_id)?;
raisfast_derive::crud_delete!(pool, "permissions", where: ("role_id", role_id))?;
Ok(())
}
+3 -3
View File
@@ -51,7 +51,7 @@ pub async fn create_token(
///
/// Returns `Ok(Some(token))` or `Ok(None)` when not found.
pub async fn find_by_token(pool: &crate::db::Pool, token: &str) -> AppResult<Option<RefreshToken>> {
raisfast_derive::crud_find!(pool, "refresh_tokens", RefreshToken, "token" => token)
raisfast_derive::crud_find!(pool, "refresh_tokens", RefreshToken, where: ("token", token))
.map_err(Into::into)
}
@@ -59,7 +59,7 @@ pub async fn find_by_token(pool: &crate::db::Pool, token: &str) -> AppResult<Opt
///
/// Used to revoke a specific refresh token on logout.
pub async fn delete_by_token(pool: &crate::db::Pool, token: &str) -> AppResult<()> {
raisfast_derive::crud_delete!(pool, "refresh_tokens", "token" => token)?;
raisfast_derive::crud_delete!(pool, "refresh_tokens", where: ("token", token))?;
Ok(())
}
@@ -67,7 +67,7 @@ pub async fn delete_by_token(pool: &crate::db::Pool, token: &str) -> AppResult<(
///
/// Used for logging out all devices or forcing re-login after a password change.
pub async fn delete_by_user(pool: &crate::db::Pool, user_id: SnowflakeId) -> AppResult<()> {
raisfast_derive::crud_delete!(pool, "refresh_tokens", "user_id" => user_id)?;
raisfast_derive::crud_delete!(pool, "refresh_tokens", where: ("user_id", user_id))?;
Ok(())
}
+3 -3
View File
@@ -31,7 +31,7 @@ pub async fn find_reusable_by_id(
tenant_id: Option<&str>,
) -> AppResult<Option<ReusableBlock>> {
Ok(
raisfast_derive::crud_find!(pool, "reusable_blocks", ReusableBlock, "id" => id, tenant: tenant_id)?,
raisfast_derive::crud_find!(pool, "reusable_blocks", ReusableBlock, where: ("id", id), tenant: tenant_id)?,
)
}
@@ -83,7 +83,7 @@ pub async fn update_reusable(
pool, "reusable_blocks",
bind: ["updated_at" => now],
optional: ["updated_by" => cmd.updated_by, "name" => cmd.name, "block_type" => cmd.block_type, "content" => cmd.content, "description" => cmd.description],
where: "id" => cmd.id,
where: ("id", cmd.id),
tenant: tenant_id
)?;
@@ -98,7 +98,7 @@ pub async fn delete_reusable(
tenant_id: Option<&str>,
) -> AppResult<()> {
let result =
raisfast_derive::crud_delete!(pool, "reusable_blocks", "id" => id, tenant: tenant_id)?;
raisfast_derive::crud_delete!(pool, "reusable_blocks", where: ("id", id), tenant: tenant_id)?;
AppError::expect_affected(&result, "reusable_block")
}
+6 -10
View File
@@ -65,14 +65,14 @@ pub async fn create(
"created_at" => now
])?;
raisfast_derive::crud_find!(pool, "sms_codes", SmsCode, "id" => id)?.ok_or_else(|| {
raisfast_derive::crud_find!(pool, "sms_codes", SmsCode, where: ("id", id))?.ok_or_else(|| {
crate::errors::app_error::AppError::Internal(anyhow::anyhow!("failed to fetch sms code"))
})
}
/// Find a verification code by ID
pub async fn find_by_id(pool: &crate::db::Pool, id: SnowflakeId) -> AppResult<Option<SmsCode>> {
Ok(raisfast_derive::crud_find!(pool, "sms_codes", SmsCode, "id" => id)?)
Ok(raisfast_derive::crud_find!(pool, "sms_codes", SmsCode, where: ("id", id))?)
}
/// Find the most recent unverified code for a phone number
@@ -85,9 +85,7 @@ pub async fn find_latest_unverified(
pool,
"sms_codes",
SmsCode,
"phone" => phone,
and: ["purpose" => purpose],
and_null: ["verified_at"],
where: AND(("phone", phone), ("purpose", purpose), ("verified_at", IS_NULL)),
order_by: "created_at DESC LIMIT 1"
)?)
}
@@ -103,9 +101,7 @@ pub async fn is_rate_limited(
let cnt = raisfast_derive::crud_count!(
pool,
"sms_codes",
"phone" => phone,
and: ["purpose" => purpose],
and_gt: ["created_at" => cutoff]
where: AND(("phone", phone), ("purpose", purpose), ("created_at", GT, cutoff))
)?;
Ok(cnt > 0)
}
@@ -136,7 +132,7 @@ pub async fn verify_code(
raisfast_derive::crud_update!(pool, "sms_codes",
bind: [],
raw: ["attempts" => "attempts + 1"],
where: "id" => id
where: ("id", id)
)?;
return Ok(VerifyResult::WrongCode);
}
@@ -144,7 +140,7 @@ pub async fn verify_code(
let now = crate::utils::tz::now_utc();
raisfast_derive::crud_update!(pool, "sms_codes",
bind: ["verified_at" => now],
where: "id" => id
where: ("id", id)
)?;
Ok(VerifyResult::Verified)
+5 -6
View File
@@ -44,9 +44,8 @@ pub async fn find_paginated(
) -> AppResult<(Vec<Tag>, i64)> {
let result = raisfast_derive::crud_query_paged!(
pool, Tag,
data_sql: "SELECT * FROM tags WHERE 1=1{tenant} ORDER BY name",
count_sql: "SELECT COUNT(*) FROM tags WHERE 1=1{tenant}",
binds: [],
table: "tags",
order_by: "name",
tenant: tenant_id,
page: page,
page_size: page_size
@@ -59,7 +58,7 @@ pub async fn find_by_id(
id: SnowflakeId,
tenant_id: Option<&str>,
) -> AppResult<Tag> {
raisfast_derive::crud_find_one!(pool, "tags", Tag, "id" => id, tenant: tenant_id)
raisfast_derive::crud_find_one!(pool, "tags", Tag, where: ("id", id), tenant: tenant_id)
.map_err(Into::into)
}
@@ -103,7 +102,7 @@ pub async fn update(
let now = crate::utils::tz::now_utc();
let result = raisfast_derive::crud_update!(pool, "tags",
bind: ["name" => name, "slug" => slug, "updated_at" => &now],
where: "id" => id,
where: ("id", id),
tenant: tenant_id
)?;
AppError::expect_affected(&result, "tag")?;
@@ -115,7 +114,7 @@ pub async fn delete(
id: SnowflakeId,
tenant_id: Option<&str>,
) -> AppResult<()> {
let result = raisfast_derive::crud_delete!(pool, "tags", "id" => id, tenant: tenant_id)?;
let result = raisfast_derive::crud_delete!(pool, "tags", where: ("id", id), tenant: tenant_id)?;
AppError::expect_affected(&result, "tag")
}
+4 -4
View File
@@ -49,13 +49,13 @@ pub async fn find_all(pool: &crate::db::Pool) -> AppResult<Vec<Tenant>> {
/// Find a tenant by integer primary key
pub async fn find_by_id(pool: &crate::db::Pool, id: SnowflakeId) -> AppResult<Option<Tenant>> {
let tenant = raisfast_derive::crud_find!(pool, "tenants", Tenant, "id" => id)?;
let tenant = raisfast_derive::crud_find!(pool, "tenants", Tenant, where: ("id", id))?;
Ok(tenant)
}
/// Find a tenant by domain
pub async fn find_by_domain(pool: &crate::db::Pool, domain: &str) -> AppResult<Option<Tenant>> {
let tenant = raisfast_derive::crud_find!(pool, "tenants", Tenant, "domain" => domain)?;
let tenant = raisfast_derive::crud_find!(pool, "tenants", Tenant, where: ("domain", domain))?;
Ok(tenant)
}
@@ -104,7 +104,7 @@ pub async fn update(
pool, "tenants",
bind: ["updated_at" => now],
optional: ["name" => name, "domain" => domain, "config" => config, "status" => status],
where: "id" => id
where: ("id", id)
)?;
find_by_id(pool, id)
@@ -114,7 +114,7 @@ pub async fn update(
/// Delete a tenant
pub async fn delete(pool: &crate::db::Pool, id: SnowflakeId) -> AppResult<()> {
raisfast_derive::crud_delete!(pool, "tenants", "id" => id)?;
raisfast_derive::crud_delete!(pool, "tenants", where: ("id", id))?;
Ok(())
}
+8 -9
View File
@@ -81,7 +81,7 @@ pub fn encode_metadata(meta: &Option<UserMetadata>) -> Option<String> {
/// Find user by username
pub async fn find_by_username(pool: &crate::db::Pool, username: &str) -> AppResult<Option<User>> {
Ok(raisfast_derive::crud_find!(pool, "users", User, "username" => username)?)
Ok(raisfast_derive::crud_find!(pool, "users", User, where: ("username", username))?)
}
/// Find user by integer primary key (internal FK lookup)
@@ -90,7 +90,7 @@ pub async fn find_by_id(
id: SnowflakeId,
tenant_id: Option<&str>,
) -> AppResult<Option<User>> {
Ok(raisfast_derive::crud_find!(pool, "users", User, "id" => id, tenant: tenant_id)?)
Ok(raisfast_derive::crud_find!(pool, "users", User, where: ("id", id), tenant: tenant_id)?)
}
/// Create a new user
@@ -119,7 +119,7 @@ pub async fn create(
tenant: tenant_id
)?;
let user = raisfast_derive::crud_find!(pool, "users", User, "id" => id, tenant: tenant_id)?
let user = raisfast_derive::crud_find!(pool, "users", User, where: ("id", id), tenant: tenant_id)?
.ok_or_else(|| AppError::Internal(anyhow::anyhow!("failed to fetch newly created user")))?;
Ok(user)
}
@@ -162,7 +162,7 @@ pub async fn update_profile(
let now = crate::utils::tz::now_utc();
raisfast_derive::crud_update!(pool, "users",
bind: ["username" => username, "bio" => bio, "website" => website, "avatar" => avatar, "social_links" => social_links, "metadata" => metadata, "updated_at" => &now],
where: "id" => user.id,
where: ("id", user.id),
tenant: tenant_id
)?;
find_by_id(pool, cmd.id, tenant_id)
@@ -179,9 +179,8 @@ pub async fn find_all(
) -> AppResult<(Vec<User>, i64)> {
let result = raisfast_derive::crud_query_paged!(
pool, User,
data_sql: "SELECT * FROM users WHERE 1=1{tenant} ORDER BY created_at DESC",
count_sql: "SELECT COUNT(*) FROM users WHERE 1=1{tenant}",
binds: [],
table: "users",
order_by: "created_at DESC",
tenant: tenant_id,
page: page,
page_size: page_size
@@ -199,7 +198,7 @@ pub async fn update_role(
let now = crate::utils::tz::now_utc();
let result = raisfast_derive::crud_update!(pool, "users",
bind: ["role" => role, "updated_at" => &now],
where: "id" => id,
where: ("id", id),
tenant: tenant_id
)?;
AppError::expect_affected(&result, "user")?;
@@ -213,7 +212,7 @@ pub async fn delete_by_id(
id: SnowflakeId,
tenant_id: Option<&str>,
) -> AppResult<()> {
raisfast_derive::crud_delete!(pool, "users", "id" => id, tenant: tenant_id)?;
raisfast_derive::crud_delete!(pool, "users", where: ("id", id), tenant: tenant_id)?;
Ok(())
}
+6 -9
View File
@@ -38,7 +38,7 @@ pub async fn find_by_id(
pool,
"user_addresses",
UserAddress,
"id" => id,
where: ("id", id),
tenant: tenant_id
)
.map_err(Into::into)
@@ -53,7 +53,7 @@ pub async fn find_by_user_id(
pool,
"user_addresses",
UserAddress,
"user_id" => user_id,
where: ("user_id", user_id),
order_by: "is_default DESC, created_at DESC",
tenant: tenant_id
)?)
@@ -68,8 +68,7 @@ pub async fn find_default_by_user(
pool,
"user_addresses",
UserAddress,
"user_id" => user_id,
and: ["is_default" => true],
where: AND(("user_id", user_id), ("is_default", true)),
tenant: tenant_id
)
.map_err(Into::into)
@@ -135,8 +134,7 @@ pub async fn update(
"address_type" => &cmd.address_type,
],
raw: ["updated_at" => crate::db::Driver::now_fn()],
where: "id" => cmd.id,
and: ["user_id" => cmd.user_id],
where: AND(("id", cmd.id), ("user_id", cmd.user_id)),
tenant: tenant_id
)?;
Ok(result.rows_affected() > 0)
@@ -151,8 +149,7 @@ pub async fn delete_by_id(
let result: crate::db::DbQueryResult = raisfast_derive::crud_delete!(
pool,
"user_addresses",
"id" => id,
and: ["user_id" => user_id],
where: AND(("id", id), ("user_id", user_id)),
tenant: tenant_id
)?;
Ok(result.rows_affected() > 0)
@@ -163,7 +160,7 @@ pub async fn count_by_user(
user_id: SnowflakeId,
tenant_id: Option<&str>,
) -> AppResult<i64> {
let count = raisfast_derive::crud_count!(pool, "user_addresses", "user_id" => user_id, tenant: tenant_id)?;
let count = raisfast_derive::crud_count!(pool, "user_addresses", where: ("user_id", user_id), tenant: tenant_id)?;
Ok(count)
}
+8 -8
View File
@@ -55,7 +55,7 @@ pub async fn find_by_auth_type_and_identifier(
auth_type: AuthType,
identifier: &str,
) -> AppResult<Option<UserCredential>> {
raisfast_derive::crud_find!(pool, "user_credentials", UserCredential, "auth_type" => auth_type, and: ["identifier" => identifier])
raisfast_derive::crud_find!(pool, "user_credentials", UserCredential, where: AND(("auth_type", auth_type), ("identifier", identifier)))
.map_err(Into::into)
}
@@ -63,12 +63,12 @@ pub async fn find_by_user_id(
pool: &crate::db::Pool,
user_id: SnowflakeId,
) -> AppResult<Vec<UserCredential>> {
raisfast_derive::crud_find_all!(pool, "user_credentials", UserCredential, "user_id" => user_id)
raisfast_derive::crud_find_all!(pool, "user_credentials", UserCredential, where: ("user_id", user_id))
.map_err(Into::into)
}
pub async fn count_by_user(pool: &crate::db::Pool, user_id: SnowflakeId) -> AppResult<i64> {
Ok(raisfast_derive::crud_count!(pool, "user_credentials", "user_id" => user_id)?)
Ok(raisfast_derive::crud_count!(pool, "user_credentials", where: ("user_id", user_id))?)
}
pub async fn create(
@@ -94,7 +94,7 @@ pub async fn create(
"updated_at" => now
])?;
let cred =
raisfast_derive::crud_find_one!(pool, "user_credentials", UserCredential, "id" => id)?;
raisfast_derive::crud_find_one!(pool, "user_credentials", UserCredential, where: ("id", id))?;
Ok(cred)
}
@@ -106,7 +106,7 @@ pub async fn update_credential_data(
let now = crate::utils::tz::now_str();
raisfast_derive::crud_update!(pool, "user_credentials",
bind: ["credential_data" => credential_data, "updated_at" => &now],
where: "id" => id
where: ("id", id)
)?;
Ok(())
}
@@ -119,13 +119,13 @@ pub async fn update_verified(
let now = crate::utils::tz::now_str();
raisfast_derive::crud_update!(pool, "user_credentials",
bind: ["verified" => if verified { 1 } else { 0 }, "updated_at" => &now],
where: "id" => id
where: ("id", id)
)?;
Ok(())
}
pub async fn delete_by_id(pool: &crate::db::Pool, id: SnowflakeId) -> AppResult<bool> {
let result = raisfast_derive::crud_delete!(pool, "user_credentials", "id" => id)?;
let result = raisfast_derive::crud_delete!(pool, "user_credentials", where: ("id", id))?;
Ok(result.rows_affected() > 0)
}
@@ -133,6 +133,6 @@ pub async fn find_by_id(
pool: &crate::db::Pool,
id: SnowflakeId,
) -> AppResult<Option<UserCredential>> {
raisfast_derive::crud_find!(pool, "user_credentials", UserCredential, "id" => id)
raisfast_derive::crud_find!(pool, "user_credentials", UserCredential, where: ("id", id))
.map_err(Into::into)
}
+6 -8
View File
@@ -29,16 +29,16 @@ pub async fn find_by_user_and_currency(
user_id: SnowflakeId,
currency: &str,
) -> AppResult<Option<Wallet>> {
raisfast_derive::crud_find!(pool, "wallets", Wallet, "user_id" => user_id, and: ["currency" => currency])
raisfast_derive::crud_find!(pool, "wallets", Wallet, where: AND(("user_id", user_id), ("currency", currency)))
.map_err(Into::into)
}
pub async fn find_by_id(pool: &crate::db::Pool, id: SnowflakeId) -> AppResult<Option<Wallet>> {
raisfast_derive::crud_find!(pool, "wallets", Wallet, "id" => id).map_err(Into::into)
raisfast_derive::crud_find!(pool, "wallets", Wallet, where: ("id", id)).map_err(Into::into)
}
pub async fn find_by_user(pool: &crate::db::Pool, user_id: SnowflakeId) -> AppResult<Vec<Wallet>> {
raisfast_derive::crud_find_all!(pool, "wallets", Wallet, "user_id" => user_id)
raisfast_derive::crud_find_all!(pool, "wallets", Wallet, where: ("user_id", user_id))
.map_err(Into::into)
}
@@ -58,7 +58,7 @@ pub async fn create(
"created_at" => now,
"updated_at" => now
])?;
raisfast_derive::crud_find_one!(pool, "wallets", Wallet, "id" => id).map_err(Into::into)
raisfast_derive::crud_find_one!(pool, "wallets", Wallet, where: ("id", id)).map_err(Into::into)
}
pub async fn find_or_create(
@@ -78,12 +78,10 @@ pub async fn find_all_wallets(
page_size: i64,
tenant_id: Option<&str>,
) -> AppResult<(Vec<Wallet>, i64)> {
raisfast_derive::check_schema!("wallets", "tenant_id", "created_at");
let result = raisfast_derive::crud_query_paged!(
pool, Wallet,
data_sql: "SELECT * FROM wallets WHERE 1=1{tenant} ORDER BY created_at DESC",
count_sql: "SELECT COUNT(*) FROM wallets WHERE 1=1{tenant}",
binds: [],
table: "wallets",
order_by: "created_at DESC",
tenant: tenant_id,
page: page,
page_size: page_size
+1 -1
View File
@@ -100,7 +100,7 @@ pub async fn mark_processing(pool: &crate::db::Pool, id: SnowflakeId) -> AppResu
pub async fn mark_completed(pool: &crate::db::Pool, id: SnowflakeId) -> AppResult<()> {
raisfast_derive::crud_update!(pool, "wallet_outbox",
raw: ["status" => "'completed'", "updated_at" => crate::db::Driver::now_fn()],
where: "id" => id
where: ("id", id)
)?;
Ok(())
}
+11 -16
View File
@@ -63,10 +63,9 @@ pub async fn find_transactions_by_wallet(
) -> AppResult<(Vec<WalletTransaction>, i64)> {
let result = raisfast_derive::crud_query_paged!(
pool, WalletTransaction,
data_sql: "SELECT * FROM wallet_transactions WHERE wallet_id = ? ORDER BY created_at DESC",
count_sql: "SELECT COUNT(*) FROM wallet_transactions WHERE wallet_id = ?",
binds: [wallet_id],
tenant: None::<&str>,
table: "wallet_transactions",
where: ("wallet_id", wallet_id),
order_by: "created_at DESC",
page: page,
page_size: page_size
);
@@ -81,10 +80,9 @@ pub async fn find_transactions_by_user(
) -> AppResult<(Vec<WalletTransaction>, i64)> {
let result = raisfast_derive::crud_query_paged!(
pool, WalletTransaction,
data_sql: "SELECT * FROM wallet_transactions WHERE user_id = ? ORDER BY created_at DESC",
count_sql: "SELECT COUNT(*) FROM wallet_transactions WHERE user_id = ?",
binds: [user_id],
tenant: None::<&str>,
table: "wallet_transactions",
where: ("user_id", user_id),
order_by: "created_at DESC",
page: page,
page_size: page_size
);
@@ -97,12 +95,10 @@ pub async fn find_all_transactions(
page_size: i64,
tenant_id: Option<&str>,
) -> AppResult<(Vec<WalletTransaction>, i64)> {
raisfast_derive::check_schema!("wallet_transactions", "tenant_id", "created_at");
let result = raisfast_derive::crud_query_paged!(
pool, WalletTransaction,
data_sql: "SELECT * FROM wallet_transactions WHERE 1=1{tenant} ORDER BY created_at DESC",
count_sql: "SELECT COUNT(*) FROM wallet_transactions WHERE 1=1{tenant}",
binds: [],
table: "wallet_transactions",
order_by: "created_at DESC",
tenant: tenant_id,
page: page,
page_size: page_size
@@ -114,7 +110,7 @@ pub async fn find_tx_by_transaction_no(
pool: &crate::db::Pool,
transaction_no: &str,
) -> AppResult<Option<WalletTransaction>> {
raisfast_derive::crud_find!(pool, "wallet_transactions", WalletTransaction, "transaction_no" => transaction_no)
raisfast_derive::crud_find!(pool, "wallet_transactions", WalletTransaction, where: ("transaction_no", transaction_no))
.map_err(Into::into)
}
@@ -122,7 +118,7 @@ pub async fn find_tx_by_id(
pool: &crate::db::Pool,
id: SnowflakeId,
) -> AppResult<Option<WalletTransaction>> {
raisfast_derive::crud_find!(pool, "wallet_transactions", WalletTransaction, "id" => id)
raisfast_derive::crud_find!(pool, "wallet_transactions", WalletTransaction, where: ("id", id))
.map_err(Into::into)
}
@@ -131,8 +127,7 @@ pub async fn has_reversal_for(
related_tx_id: SnowflakeId,
) -> AppResult<bool> {
Ok(raisfast_derive::crud_exists!(
pool, "wallet_transactions", "related_tx_id" => related_tx_id,
and: ["tx_type" => WalletTxType::Refund]
pool, "wallet_transactions", where: AND(("related_tx_id", related_tx_id), ("tx_type", WalletTxType::Refund))
)?)
}
+1 -1
View File
@@ -391,7 +391,7 @@ pub async fn refresh(
let now = crate::utils::tz::now_str();
in_transaction!(pool, tx, {
raisfast_derive::crud_delete!(&mut *tx, "refresh_tokens", "token" => refresh_token_str)?;
raisfast_derive::crud_delete!(&mut *tx, "refresh_tokens", where: ("token", refresh_token_str))?;
raisfast_derive::crud_insert!(&mut *tx, "refresh_tokens", [
"id" => new_id,
+2 -3
View File
@@ -43,14 +43,13 @@ pub async fn verify_email(pool: &crate::db::Pool, token: &str) -> AppResult<()>
let now = crate::utils::tz::now_utc();
raisfast_derive::crud_update!(&mut *tx, "email_verification_tokens",
bind: ["verified_at" => now],
where: "id" => verification.id
where: ("id", verification.id)
)?;
let now = crate::utils::tz::now_str();
raisfast_derive::crud_update!(&mut *tx, "user_credentials",
bind: ["verified" => 1i64, "updated_at" => &now],
where: "user_id" => verification.user_id,
and: ["auth_type" => crate::models::user_credential::AuthType::Email]
where: AND(("user_id", verification.user_id), ("auth_type", crate::models::user_credential::AuthType::Email))
)?;
Ok::<_, crate::errors::app_error::AppError>(())
})?;
+1 -1
View File
@@ -327,7 +327,7 @@ async fn auto_register_user(
let now = crate::utils::tz::now_str();
raisfast_derive::crud_update!(pool, "users",
bind: ["avatar" => avatar, "updated_at" => &now],
where: "id" => user.id
where: ("id", user.id)
)?;
}
+4 -5
View File
@@ -68,24 +68,23 @@ pub async fn reset_password(
&mut *tx,
"user_credentials",
(SnowflakeId, crate::models::user_credential::AuthType),
"user_id" => reset_token.user_id,
and: ["auth_type" => crate::models::user_credential::AuthType::Email]
where: AND(("user_id", reset_token.user_id), ("auth_type", crate::models::user_credential::AuthType::Email))
)?;
if let Some((cred_id, _)) = row.into_iter().next() {
let now = crate::utils::tz::now_str();
raisfast_derive::crud_update!(&mut *tx, "user_credentials",
bind: ["credential_data" => crate::models::user_credential::wrap_password_hash(&new_hash), "updated_at" => &now],
where: "id" => cred_id
where: ("id", cred_id)
)?;
}
let now = crate::utils::tz::now_utc();
raisfast_derive::crud_update!(&mut *tx, "password_reset_tokens",
bind: ["used_at" => now],
where: "id" => reset_token.id
where: ("id", reset_token.id)
)?;
raisfast_derive::crud_delete!(&mut *tx, "refresh_tokens", "user_id" => reset_token.user_id)?;
raisfast_derive::crud_delete!(&mut *tx, "refresh_tokens", where: ("user_id", reset_token.user_id))?;
Ok::<_, crate::errors::app_error::AppError>(())
})?;
+8 -11
View File
@@ -165,7 +165,7 @@ async fn tx_find_wallet_by_id(
tx: &mut DbConnection,
id: SnowflakeId,
) -> AppResult<Option<wallet::Wallet>> {
Ok(raisfast_derive::crud_find!(tx, "wallets", wallet::Wallet, "id" => id)?)
Ok(raisfast_derive::crud_find!(tx, "wallets", wallet::Wallet, where: ("id", id))?)
}
async fn tx_find_or_create(
@@ -174,8 +174,7 @@ async fn tx_find_or_create(
currency: &str,
) -> AppResult<wallet::Wallet> {
if let Some(w) = raisfast_derive::crud_find!(
&mut *tx, "wallets", wallet::Wallet, "user_id" => user_id,
and: ["currency" => currency]
&mut *tx, "wallets", wallet::Wallet, where: AND(("user_id", user_id), ("currency", currency))
)? {
return Ok(w);
}
@@ -190,11 +189,10 @@ async fn tx_find_or_create(
match insert_result {
Ok(_) => {
Ok(raisfast_derive::crud_find_one!(&mut *tx, "wallets", wallet::Wallet, "id" => id)?)
Ok(raisfast_derive::crud_find_one!(&mut *tx, "wallets", wallet::Wallet, where: ("id", id))?)
}
Err(_) => Ok(raisfast_derive::crud_find_one!(
&mut *tx, "wallets", wallet::Wallet, "user_id" => user_id,
and: ["currency" => currency]
&mut *tx, "wallets", wallet::Wallet, where: AND(("user_id", user_id), ("currency", currency))
)?),
}
}
@@ -203,7 +201,7 @@ async fn tx_find_tx_by_id(
tx: &mut DbConnection,
id: SnowflakeId,
) -> AppResult<Option<WalletTransaction>> {
Ok(raisfast_derive::crud_find!(tx, "wallet_transactions", WalletTransaction, "id" => id)?)
Ok(raisfast_derive::crud_find!(tx, "wallet_transactions", WalletTransaction, where: ("id", id))?)
}
async fn tx_find_tx_by_transaction_no(
@@ -211,14 +209,13 @@ async fn tx_find_tx_by_transaction_no(
transaction_no: &str,
) -> AppResult<Option<WalletTransaction>> {
Ok(
raisfast_derive::crud_find!(tx, "wallet_transactions", WalletTransaction, "transaction_no" => transaction_no)?,
raisfast_derive::crud_find!(tx, "wallet_transactions", WalletTransaction, where: ("transaction_no", transaction_no))?,
)
}
async fn tx_has_reversal_for(tx: &mut DbConnection, related_tx_id: SnowflakeId) -> AppResult<bool> {
Ok(raisfast_derive::crud_exists!(
tx, "wallet_transactions", "related_tx_id" => related_tx_id,
and: ["tx_type" => WalletTxType::Refund]
tx, "wallet_transactions", where: AND(("related_tx_id", related_tx_id), ("tx_type", WalletTxType::Refund))
)?)
}
@@ -684,7 +681,7 @@ async fn insert_tx(
["id" => id, "wallet_id" => wallet_id, "user_id" => user_id, "entry_type" => entry_type, "amount" => amount, "balance_after" => balance_after, "tx_type" => tx_type, "currency" => currency, "transaction_no" => transaction_no, "related_tx_id" => related_tx_id, "reference_type" => reference_type, "reference_id" => reference_id, "counterparty_wallet_id" => counterparty_wallet_id, "metadata" => metadata, "created_at" => now]
)?;
let row = raisfast_derive::crud_find_one!(&mut *tx, "wallet_transactions", WalletTransaction, "id" => id)?;
let row = raisfast_derive::crud_find_one!(&mut *tx, "wallet_transactions", WalletTransaction, where: ("id", id))?;
Ok(row)
}
+6 -7
View File
@@ -74,9 +74,8 @@ pub async fn find_paginated(
) -> AppResult<(Vec<WebhookSubscription>, i64)> {
let result = raisfast_derive::crud_query_paged!(
pool, WebhookSubscription,
data_sql: "SELECT * FROM webhook_subscriptions WHERE 1=1{tenant} ORDER BY created_at DESC",
count_sql: "SELECT COUNT(*) FROM webhook_subscriptions WHERE 1=1{tenant}",
binds: [],
table: "webhook_subscriptions",
order_by: "created_at DESC",
tenant: tenant_id,
page: page,
page_size: page_size
@@ -85,7 +84,7 @@ pub async fn find_paginated(
}
pub async fn find_by_id(pool: &crate::db::Pool, id: SnowflakeId) -> AppResult<WebhookSubscription> {
raisfast_derive::crud_find_one!(pool, "webhook_subscriptions", WebhookSubscription, "id" => id)
raisfast_derive::crud_find_one!(pool, "webhook_subscriptions", WebhookSubscription, where: ("id", id))
.map_err(Into::into)
}
@@ -94,14 +93,14 @@ pub async fn update(pool: &crate::db::Pool, sub: &WebhookSubscription) -> AppRes
let result = raisfast_derive::crud_update!(
pool, "webhook_subscriptions",
bind: ["url" => &sub.url, "secret" => &sub.secret, "events" => &sub.events, "enabled" => sub.enabled, "description" => &sub.description, "updated_at" => now],
where: "id" => sub.id
where: ("id", sub.id)
)?;
AppError::expect_affected(&result, "webhook_subscription")?;
Ok(())
}
pub async fn delete_by_id(pool: &crate::db::Pool, id: SnowflakeId) -> AppResult<()> {
let result = raisfast_derive::crud_delete!(pool, "webhook_subscriptions", "id" => id)?;
let result = raisfast_derive::crud_delete!(pool, "webhook_subscriptions", where: ("id", id))?;
AppError::expect_affected(&result, "webhook_subscription")?;
Ok(())
}
@@ -111,6 +110,6 @@ pub async fn find_enabled_by_tenant(
tenant_id: Option<&str>,
) -> AppResult<Vec<WebhookSubscription>> {
Ok(
raisfast_derive::crud_find_all!(pool, "webhook_subscriptions", WebhookSubscription, "enabled" => true, tenant: tenant_id)?,
raisfast_derive::crud_find_all!(pool, "webhook_subscriptions", WebhookSubscription, where: ("enabled", true), tenant: tenant_id)?,
)
}
+6 -7
View File
@@ -115,7 +115,7 @@ impl JobQueue for DefaultJobQueue {
.map_err(|e| AppError::Internal(anyhow::anyhow!("invalid id: {e}")))?;
raisfast_derive::crud_update!(&self.pool, "jobs",
bind: ["status" => JobStatus::Completed, "updated_at" => now],
where: "id" => id
where: ("id", id)
)?;
tracing::debug!("job {id} completed");
Ok(())
@@ -144,7 +144,7 @@ impl JobQueue for DefaultJobQueue {
if attempts >= max_attempts {
raisfast_derive::crud_update!(&mut *tx, "jobs",
bind: ["status" => JobStatus::Dead, "error" => error, "updated_at" => now],
where: "id" => id
where: ("id", id)
)?;
tracing::error!("job {id} dead: {error}");
return Ok::<_, AppError>(());
@@ -156,7 +156,7 @@ impl JobQueue for DefaultJobQueue {
raisfast_derive::crud_update!(&mut *tx, "jobs",
bind: ["status" => JobStatus::Pending, "error" => error, "run_after" => run_after, "updated_at" => now],
where: "id" => id
where: ("id", id)
)?;
tracing::warn!(
@@ -173,7 +173,7 @@ impl JobQueue for DefaultJobQueue {
.map_err(|e| AppError::Internal(anyhow::anyhow!("invalid id: {e}")))?;
raisfast_derive::crud_update!(&self.pool, "jobs",
bind: ["status" => JobStatus::Dead, "error" => error, "updated_at" => now],
where: "id" => id
where: ("id", id)
)?;
tracing::error!("job {id} dead: {error}");
Ok(())
@@ -314,8 +314,7 @@ impl JobQueue for DefaultJobQueue {
"run_after" => None::<crate::utils::tz::Timestamp>,
"updated_at" => now
],
where: "id" => id,
and: ["status" => JobStatus::Dead]
where: AND(("id", id), ("status", JobStatus::Dead))
)?;
if result.rows_affected() == 0 {
@@ -331,7 +330,7 @@ impl JobQueue for DefaultJobQueue {
.parse()
.map_err(|e| AppError::Internal(anyhow::anyhow!("invalid id: {e}")))?;
let result: crate::db::DbQueryResult =
raisfast_derive::crud_delete!(&self.pool, "jobs", "id" => id)?;
raisfast_derive::crud_delete!(&self.pool, "jobs", where: ("id", id))?;
if result.rows_affected() == 0 {
return Err(AppError::not_found("job"));
+11 -11
View File
@@ -235,7 +235,7 @@ pub async fn toggle_schedule(pool: &Pool, id: SnowflakeId, enabled: bool) -> App
let now = crate::utils::tz::now_utc();
let result = raisfast_derive::crud_update!(pool, "cron_schedules",
bind: ["enabled" => enabled, "updated_at" => now],
where: "id" => id
where: ("id", id)
)?;
if result.rows_affected() == 0 {
@@ -289,7 +289,7 @@ pub async fn update_schedule(
"next_run_at" => next,
"updated_at" => now
],
where: "id" => id
where: ("id", id)
)?;
Ok(find_by_id(pool, id).await?.unwrap_or(schedule))
@@ -298,7 +298,7 @@ pub async fn update_schedule(
/// Delete a schedule
pub async fn delete_schedule(pool: &Pool, id: SnowflakeId) -> AppResult<()> {
let result: crate::db::DbQueryResult =
raisfast_derive::crud_delete!(pool, "cron_schedules", "id" => id)?;
raisfast_derive::crud_delete!(pool, "cron_schedules", where: ("id", id))?;
if result.rows_affected() == 0 {
return Err(AppError::not_found("cron_schedule"));
@@ -404,7 +404,7 @@ impl CronScheduler {
if let Some(ref lid) = log_id {
raisfast_derive::crud_update!(&mut *tx, "cron_execution_log",
bind: ["status" => CronExecStatus::Completed, "duration_ms" => elapsed, "finished_at" => now],
where: "id" => lid
where: ("id", lid)
)?;
}
}
@@ -413,7 +413,7 @@ impl CronScheduler {
let err_str = e.to_string();
raisfast_derive::crud_update!(&mut *tx, "cron_execution_log",
bind: ["status" => CronExecStatus::Failed, "duration_ms" => elapsed, "error" => &err_str, "finished_at" => now],
where: "id" => lid
where: ("id", lid)
)?;
}
tracing::error!("cron dispatch failed for '{}': {e}", schedule.label);
@@ -423,7 +423,7 @@ impl CronScheduler {
if let Some(next) = &next {
raisfast_derive::crud_update!(&mut *tx, "cron_schedules",
bind: ["last_run_at" => now, "next_run_at" => next, "updated_at" => now],
where: "id" => schedule.id
where: ("id", schedule.id)
)?;
}
@@ -542,7 +542,7 @@ pub async fn complete_execution_log(
let now = crate::utils::tz::now_utc();
raisfast_derive::crud_update!(pool, "cron_execution_log",
bind: ["status" => CronExecStatus::Completed, "duration_ms" => duration_ms, "finished_at" => now],
where: "id" => log_id
where: ("id", log_id)
)?;
Ok(())
}
@@ -557,7 +557,7 @@ pub async fn fail_execution_log(
let now = crate::utils::tz::now_utc();
raisfast_derive::crud_update!(pool, "cron_execution_log",
bind: ["status" => CronExecStatus::Failed, "duration_ms" => duration_ms, "error" => error, "finished_at" => now],
where: "id" => log_id
where: ("id", log_id)
)?;
Ok(())
}
@@ -647,7 +647,7 @@ pub async fn sync_plugin_crons(
for row in &old {
if !new_types.contains(&row.job_type.as_str()) {
raisfast_derive::crud_delete!(&mut *tx, "cron_schedules", "id" => row.id)?;
raisfast_derive::crud_delete!(&mut *tx, "cron_schedules", where: ("id", row.id))?;
tracing::info!(
"removed stale cron '{}' for plugin {plugin_id}",
row.job_type
@@ -671,7 +671,7 @@ pub async fn sync_plugin_crons(
let next = next_run(&entry.cron_expr, crate::utils::tz::now_utc())?;
raisfast_derive::crud_update!(&mut *tx, "cron_schedules",
bind: ["label" => &entry.label, "payload" => &entry.payload, "cron_expr" => &entry.cron_expr, "enabled" => entry.enabled, "next_run_at" => next, "updated_at" => now],
where: "id" => existing_row.0
where: ("id", existing_row.0)
)?;
tracing::debug!("updated cron '{}' for plugin {plugin_id}", entry.job_type);
@@ -705,7 +705,7 @@ pub async fn sync_plugin_crons(
/// Called when a plugin is unloaded.
pub async fn remove_plugin_crons(pool: &Pool, plugin_id: &str) -> AppResult<()> {
let result: crate::db::DbQueryResult =
raisfast_derive::crud_delete!(pool, "cron_schedules", "plugin_id" => plugin_id)?;
raisfast_derive::crud_delete!(pool, "cron_schedules", where: ("plugin_id", plugin_id))?;
let count = result.rows_affected();
if count > 0 {
+7 -7
View File
@@ -148,7 +148,7 @@ pub async fn get_definition(
pool: &Pool,
id: SnowflakeId,
) -> anyhow::Result<Option<WorkflowDefinition>> {
Ok(raisfast_derive::crud_find!(pool, "workflow_definitions", WorkflowDefinition, "id" => id)?)
Ok(raisfast_derive::crud_find!(pool, "workflow_definitions", WorkflowDefinition, where: ("id", id))?)
}
/// List all workflow definitions
@@ -160,7 +160,7 @@ pub async fn list_definitions(pool: &Pool) -> anyhow::Result<Vec<WorkflowDefinit
/// Delete workflow definition
pub async fn delete_definition(pool: &Pool, id: SnowflakeId) -> anyhow::Result<()> {
raisfast_derive::crud_delete!(pool, "workflow_definitions", "id" => id)?;
raisfast_derive::crud_delete!(pool, "workflow_definitions", where: ("id", id))?;
Ok(())
}
@@ -188,7 +188,7 @@ pub async fn get_instance(
pool: &Pool,
id: SnowflakeId,
) -> anyhow::Result<Option<WorkflowInstance>> {
Ok(raisfast_derive::crud_find!(pool, "workflow_instances", WorkflowInstance, "id" => id)?)
Ok(raisfast_derive::crud_find!(pool, "workflow_instances", WorkflowInstance, where: ("id", id))?)
}
/// List workflow instances
@@ -291,7 +291,7 @@ pub async fn create_step_log(
pool, "workflow_step_logs",
["id" => id, "instance_id" => instance_id, "step_id" => step_id, "step_name" => step_name, "status" => WorkflowStepStatus::Running, "input" => input_str, "started_at" => now]
)?;
raisfast_derive::crud_find_one!(pool, "workflow_step_logs", StepLog, "id" => id)
raisfast_derive::crud_find_one!(pool, "workflow_step_logs", StepLog, where: ("id", id))
.map_err(|e| anyhow::anyhow!("failed to fetch created step log: {e}"))
}
@@ -306,7 +306,7 @@ pub async fn complete_step_log(
raisfast_derive::crud_update!(
pool, "workflow_step_logs",
bind: ["status" => WorkflowStepStatus::Completed, "output" => output_str, "completed_at" => now],
where: "id" => id
where: ("id", id)
)?;
Ok(())
}
@@ -317,7 +317,7 @@ pub async fn fail_step_log(pool: &Pool, id: SnowflakeId, error: &str) -> anyhow:
raisfast_derive::crud_update!(
pool, "workflow_step_logs",
bind: ["status" => WorkflowStepStatus::Failed, "error" => error, "completed_at" => now],
where: "id" => id
where: ("id", id)
)?;
Ok(())
}
@@ -325,7 +325,7 @@ pub async fn fail_step_log(pool: &Pool, id: SnowflakeId, error: &str) -> anyhow:
/// List step logs for an instance
pub async fn list_step_logs(pool: &Pool, instance_id: SnowflakeId) -> anyhow::Result<Vec<StepLog>> {
Ok(
raisfast_derive::crud_find_all!(pool, "workflow_step_logs", StepLog, "instance_id" => instance_id, order_by: "started_at ASC")?,
raisfast_derive::crud_find_all!(pool, "workflow_step_logs", StepLog, where: ("instance_id", instance_id), order_by: "started_at ASC")?,
)
}