forked from StellarSend/backend
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtransaction.rs
More file actions
154 lines (146 loc) · 5.27 KB
/
Copy pathtransaction.rs
File metadata and controls
154 lines (146 loc) · 5.27 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
/// All possible states a StellarSend transaction can be in.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, sqlx::Type)]
#[sqlx(type_name = "transaction_status", rename_all = "lowercase")]
#[serde(rename_all = "lowercase")]
pub enum TransactionStatus {
Pending,
Submitted,
/// Submission's outcome is unknown — Horizon never gave us a definitive
/// answer (client-side timeout, connection drop, or a crash before our
/// post-submission UPDATE committed). Distinct from `Failed`: the
/// transaction may still have landed on-chain, so it's never safe to
/// treat this the same as a definite rejection or let a caller blindly
/// retry. Resolved by `ReconciliationService` querying Horizon directly
/// by the pre-submission-computed hash (#30).
#[sqlx(rename = "submitted_unconfirmed")]
#[serde(rename = "submitted_unconfirmed")]
SubmittedUnconfirmed,
Completed,
Failed,
Expired,
}
impl std::fmt::Display for TransactionStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Pending => write!(f, "pending"),
Self::Submitted => write!(f, "submitted"),
Self::SubmittedUnconfirmed => write!(f, "submitted_unconfirmed"),
Self::Completed => write!(f, "completed"),
Self::Failed => write!(f, "failed"),
Self::Expired => write!(f, "expired"),
}
}
}
/// Raw database row for a transaction.
#[derive(Debug, Clone, sqlx::FromRow)]
pub struct TransactionRow {
pub id: Uuid,
pub user_id: Uuid,
pub stellar_tx_hash: Option<String>,
pub from_asset: String,
pub to_asset: String,
pub send_amount: String,
pub receive_amount: Option<String>,
pub source_account: String,
pub destination_account: String,
pub status: TransactionStatus,
pub error_message: Option<String>,
pub fee_xlm: Option<String>,
pub path: Option<serde_json::Value>,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
/// Public-facing transaction.
#[derive(Debug, Clone, Serialize)]
pub struct Transaction {
pub id: Uuid,
pub user_id: Uuid,
pub stellar_tx_hash: Option<String>,
pub from_asset: String,
pub to_asset: String,
pub send_amount: String,
pub receive_amount: Option<String>,
pub source_account: String,
pub destination_account: String,
pub status: TransactionStatus,
pub error_message: Option<String>,
pub fee_xlm: Option<String>,
pub path: Option<serde_json::Value>,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
impl From<TransactionRow> for Transaction {
fn from(row: TransactionRow) -> Self {
Self {
id: row.id,
user_id: row.user_id,
stellar_tx_hash: row.stellar_tx_hash,
from_asset: row.from_asset,
to_asset: row.to_asset,
send_amount: row.send_amount,
receive_amount: row.receive_amount,
source_account: row.source_account,
destination_account: row.destination_account,
status: row.status,
error_message: row.error_message,
fee_xlm: row.fee_xlm,
path: row.path,
created_at: row.created_at,
updated_at: row.updated_at,
}
}
}
/// Data needed to create a new transaction record.
#[derive(Debug, Clone)]
pub struct CreateTransaction {
pub user_id: Uuid,
pub from_asset: String,
pub to_asset: String,
pub send_amount: String,
pub receive_amount: Option<String>,
pub source_account: String,
pub destination_account: String,
pub fee_xlm: Option<String>,
pub path: Option<serde_json::Value>,
}
/// Query parameters for listing transactions.
///
/// Two pagination modes:
/// - **Cursor (default, cheap).** Pass `cursor` (opaque, from a previous
/// response's `next_cursor`) or omit both `cursor` and `page` for the
/// first page. Never runs a `COUNT(*)`.
/// - **Legacy offset (`page`/`per_page`).** Pass `page` to keep the old
/// page-number behavior, including its `COUNT(*)`-per-request cost. Kept
/// for existing consumers during the transition — see README/API docs.
///
/// `include_total=true` opts either mode into also computing `total`/
/// `total_pages` (always computed in legacy mode regardless of this flag).
#[derive(Debug, Deserialize)]
pub struct TransactionListParams {
pub status: Option<TransactionStatus>,
pub from_asset: Option<String>,
pub to_asset: Option<String>,
pub page: Option<u32>,
pub per_page: Option<u32>,
pub cursor: Option<String>,
pub include_total: Option<bool>,
}
/// Paginated response.
///
/// `total`/`page`/`total_pages` are `None` in cursor mode unless
/// `include_total=true` was requested — computing them costs a full
/// `COUNT(*)`, which cursor mode exists specifically to avoid by default.
/// `next_cursor` is `None` in legacy offset mode and in cursor mode once
/// the last page has been reached.
#[derive(Debug, Serialize)]
pub struct PaginatedTransactions {
pub items: Vec<Transaction>,
pub total: Option<i64>,
pub page: Option<u32>,
pub per_page: u32,
pub total_pages: Option<u32>,
pub next_cursor: Option<String>,
}