1use std::sync::Arc;
2
3use serde::de::DeserializeOwned;
4use serde_json::{json, Value};
5
6use crate::client::ClientInner;
7use crate::error::Result;
8use crate::types::{
9 AccountHistoryEntry, AccountReputation, ActiveVote, AppliedOperation, BlockHeader,
10 CollateralizedConversionRequest, Comment, Discussion, DiscussionQuery, DiscussionQueryCategory,
11 DynamicGlobalProperties, Escrow, ExpiringVestingDelegation, ExtendedAccount, FeedHistory,
12 FollowCount, FollowEntry, MarketBucket, MarketTrade, OpenOrder, OrderBook, OwnerHistory, Price,
13 Proposal, RecoveryRequest, RecurrentTransfer, RewardFund, SavingsWithdraw, ScheduledHardfork,
14 SignedBlock, SignedTransaction, Version, VestingDelegation, Witness,
15};
16
17#[derive(Debug, Clone)]
18pub struct DatabaseApi {
19 client: Arc<ClientInner>,
20}
21
22impl DatabaseApi {
23 pub(crate) fn new(client: Arc<ClientInner>) -> Self {
24 Self { client }
25 }
26
27 async fn call<T: DeserializeOwned>(&self, method: &str, params: Value) -> Result<T> {
28 self.client.call("condenser_api", method, params).await
29 }
30
31 pub async fn get_accounts(&self, accounts: &[&str]) -> Result<Vec<ExtendedAccount>> {
32 self.call("get_accounts", json!([accounts])).await
33 }
34
35 pub async fn get_account_count(&self) -> Result<u64> {
36 self.call("get_account_count", json!([])).await
37 }
38
39 pub async fn get_account_history(
40 &self,
41 account: &str,
42 start: i64,
43 limit: u32,
44 ) -> Result<Vec<AccountHistoryEntry>> {
45 self.call("get_account_history", json!([account, start, limit]))
46 .await
47 }
48
49 pub async fn get_account_reputations(
50 &self,
51 account_lower_bound: &str,
52 limit: u32,
53 ) -> Result<Vec<AccountReputation>> {
54 self.call(
55 "get_account_reputations",
56 json!([account_lower_bound, limit]),
57 )
58 .await
59 }
60
61 pub async fn get_owner_history(&self, account: &str) -> Result<Vec<OwnerHistory>> {
62 self.call("get_owner_history", json!([account])).await
63 }
64
65 pub async fn get_recovery_request(&self, account: &str) -> Result<Option<RecoveryRequest>> {
66 self.call("get_recovery_request", json!([account])).await
67 }
68
69 pub async fn get_content(&self, author: &str, permlink: &str) -> Result<Comment> {
70 self.call("get_content", json!([author, permlink])).await
71 }
72
73 pub async fn get_content_replies(&self, author: &str, permlink: &str) -> Result<Vec<Comment>> {
74 self.call("get_content_replies", json!([author, permlink]))
75 .await
76 }
77
78 pub async fn get_discussions(
79 &self,
80 by: DiscussionQueryCategory,
81 query: &DiscussionQuery,
82 ) -> Result<Vec<Discussion>> {
83 let method = match by {
84 DiscussionQueryCategory::Trending => "get_discussions_by_trending",
85 DiscussionQueryCategory::Created => "get_discussions_by_created",
86 DiscussionQueryCategory::Active => "get_discussions_by_active",
87 DiscussionQueryCategory::Cashout => "get_discussions_by_cashout",
88 DiscussionQueryCategory::Payout => "get_post_discussions_by_payout",
89 DiscussionQueryCategory::Votes => "get_discussions_by_votes",
90 DiscussionQueryCategory::Children => "get_discussions_by_children",
91 DiscussionQueryCategory::Hot => "get_discussions_by_hot",
92 DiscussionQueryCategory::Feed => "get_discussions_by_feed",
93 DiscussionQueryCategory::Blog => "get_discussions_by_blog",
94 DiscussionQueryCategory::Comments => "get_discussions_by_comments",
95 DiscussionQueryCategory::Promoted => "get_discussions_by_promoted",
96 DiscussionQueryCategory::Replies => "get_replies_by_last_update",
97 };
98
99 self.call(method, json!([query])).await
100 }
101
102 pub async fn get_discussions_by_author_before_date(
103 &self,
104 author: &str,
105 start_permlink: &str,
106 before_date: &str,
107 limit: u32,
108 ) -> Result<Vec<Discussion>> {
109 self.call(
110 "get_discussions_by_author_before_date",
111 json!([author, start_permlink, before_date, limit]),
112 )
113 .await
114 }
115
116 pub async fn get_active_votes(&self, author: &str, permlink: &str) -> Result<Vec<ActiveVote>> {
117 self.call("get_active_votes", json!([author, permlink]))
118 .await
119 }
120
121 pub async fn get_dynamic_global_properties(&self) -> Result<DynamicGlobalProperties> {
122 self.call("get_dynamic_global_properties", json!([])).await
123 }
124
125 pub async fn get_chain_properties(&self) -> Result<Value> {
126 self.call("get_chain_properties", json!([])).await
127 }
128
129 pub async fn get_feed_history(&self) -> Result<FeedHistory> {
130 self.call("get_feed_history", json!([])).await
131 }
132
133 pub async fn get_current_median_history_price(&self) -> Result<Price> {
134 self.call("get_current_median_history_price", json!([]))
135 .await
136 }
137
138 pub async fn get_hardfork_version(&self) -> Result<String> {
139 self.call("get_hardfork_version", json!([])).await
140 }
141
142 pub async fn get_next_scheduled_hardfork(&self) -> Result<ScheduledHardfork> {
143 self.call("get_next_scheduled_hardfork", json!([])).await
144 }
145
146 pub async fn get_reward_fund(&self, name: &str) -> Result<RewardFund> {
147 self.call("get_reward_fund", json!([name])).await
148 }
149
150 pub async fn get_config(&self) -> Result<Value> {
151 self.call("get_config", json!([])).await
152 }
153
154 pub async fn get_version(&self) -> Result<Version> {
155 self.call("get_version", json!([])).await
156 }
157
158 pub async fn get_active_witnesses(&self) -> Result<Vec<String>> {
159 self.call("get_active_witnesses", json!([])).await
160 }
161
162 pub async fn get_witness_by_account(&self, account: &str) -> Result<Option<Witness>> {
163 self.call("get_witness_by_account", json!([account])).await
164 }
165
166 pub async fn get_vesting_delegations(
167 &self,
168 account: &str,
169 from: &str,
170 limit: u32,
171 ) -> Result<Vec<VestingDelegation>> {
172 self.call("get_vesting_delegations", json!([account, from, limit]))
173 .await
174 }
175
176 pub async fn get_expiring_vesting_delegations(
177 &self,
178 account: &str,
179 from: &str,
180 limit: u32,
181 ) -> Result<Vec<ExpiringVestingDelegation>> {
182 self.call(
183 "get_expiring_vesting_delegations",
184 json!([account, from, limit]),
185 )
186 .await
187 }
188
189 pub async fn get_order_book(&self, limit: u32) -> Result<OrderBook> {
190 self.call("get_order_book", json!([limit])).await
191 }
192
193 pub async fn get_open_orders(&self, account: &str) -> Result<Vec<OpenOrder>> {
194 self.call("get_open_orders", json!([account])).await
195 }
196
197 pub async fn get_recent_trades(&self, limit: u32) -> Result<Vec<MarketTrade>> {
198 self.call("get_recent_trades", json!([limit])).await
199 }
200
201 pub async fn get_market_history(
202 &self,
203 bucket_seconds: u32,
204 start: &str,
205 end: &str,
206 ) -> Result<Vec<MarketBucket>> {
207 self.call("get_market_history", json!([bucket_seconds, start, end]))
208 .await
209 }
210
211 pub async fn get_market_history_buckets(&self) -> Result<Vec<u32>> {
212 self.call("get_market_history_buckets", json!([])).await
213 }
214
215 pub async fn get_savings_withdraw_from(&self, account: &str) -> Result<Vec<SavingsWithdraw>> {
216 self.call("get_savings_withdraw_from", json!([account]))
217 .await
218 }
219
220 pub async fn get_savings_withdraw_to(&self, account: &str) -> Result<Vec<SavingsWithdraw>> {
221 self.call("get_savings_withdraw_to", json!([account])).await
222 }
223
224 pub async fn get_conversion_requests(&self, account: &str) -> Result<Vec<Value>> {
225 self.call("get_conversion_requests", json!([account])).await
226 }
227
228 pub async fn get_collateralized_conversion_requests(
229 &self,
230 account: &str,
231 ) -> Result<Vec<CollateralizedConversionRequest>> {
232 self.call("get_collateralized_conversion_requests", json!([account]))
233 .await
234 }
235
236 pub async fn get_followers(
237 &self,
238 account: &str,
239 start_follower: &str,
240 follow_type: &str,
241 limit: u32,
242 ) -> Result<Vec<FollowEntry>> {
243 self.call(
244 "get_followers",
245 json!([account, start_follower, follow_type, limit]),
246 )
247 .await
248 }
249
250 pub async fn get_following(
251 &self,
252 account: &str,
253 start_following: &str,
254 follow_type: &str,
255 limit: u32,
256 ) -> Result<Vec<FollowEntry>> {
257 self.call(
258 "get_following",
259 json!([account, start_following, follow_type, limit]),
260 )
261 .await
262 }
263
264 pub async fn get_follow_count(&self, account: &str) -> Result<FollowCount> {
265 self.call("get_follow_count", json!([account])).await
266 }
267
268 pub async fn get_reblogged_by(&self, author: &str, permlink: &str) -> Result<Vec<String>> {
269 self.call("get_reblogged_by", json!([author, permlink]))
270 .await
271 }
272
273 pub async fn get_blog(
274 &self,
275 account: &str,
276 start_entry_id: u32,
277 limit: u32,
278 ) -> Result<Vec<Discussion>> {
279 self.call("get_blog", json!([account, start_entry_id, limit]))
280 .await
281 }
282
283 pub async fn get_blog_entries(
284 &self,
285 account: &str,
286 start_entry_id: u32,
287 limit: u32,
288 ) -> Result<Vec<Value>> {
289 self.call("get_blog_entries", json!([account, start_entry_id, limit]))
290 .await
291 }
292
293 pub async fn get_potential_signatures(
294 &self,
295 transaction: &SignedTransaction,
296 ) -> Result<Vec<String>> {
297 self.call("get_potential_signatures", json!([transaction]))
298 .await
299 }
300
301 pub async fn get_required_signatures(
302 &self,
303 transaction: &SignedTransaction,
304 available_keys: &[String],
305 ) -> Result<Vec<String>> {
306 self.call(
307 "get_required_signatures",
308 json!([transaction, available_keys]),
309 )
310 .await
311 }
312
313 pub async fn verify_authority(&self, transaction: &SignedTransaction) -> Result<bool> {
314 self.call("verify_authority", json!([transaction])).await
315 }
316
317 pub async fn get_key_references(&self, keys: &[String]) -> Result<Vec<Vec<String>>> {
318 self.call("get_key_references", json!([keys])).await
319 }
320
321 pub async fn get_escrow(&self, from: &str, escrow_id: u32) -> Result<Option<Escrow>> {
322 self.call("get_escrow", json!([from, escrow_id])).await
323 }
324
325 pub async fn find_proposals(&self, proposal_ids: &[i64]) -> Result<Vec<Proposal>> {
326 self.call("find_proposals", json!([proposal_ids])).await
327 }
328
329 pub async fn list_proposals(
330 &self,
331 start: Value,
332 limit: u32,
333 order_by: &str,
334 order_direction: &str,
335 status: &str,
336 ) -> Result<Vec<Proposal>> {
337 self.call(
338 "list_proposals",
339 json!([start, limit, order_by, order_direction, status]),
340 )
341 .await
342 }
343
344 pub async fn find_recurrent_transfers(&self, account: &str) -> Result<Vec<RecurrentTransfer>> {
345 self.call("find_recurrent_transfers", json!([account]))
346 .await
347 }
348
349 pub async fn get_ops_in_block(
350 &self,
351 block_num: u32,
352 only_virtual: bool,
353 ) -> Result<Vec<AppliedOperation>> {
354 self.call("get_ops_in_block", json!([block_num, only_virtual]))
355 .await
356 }
357
358 pub async fn get_operations(&self, block_num: u32) -> Result<Vec<AppliedOperation>> {
359 self.get_ops_in_block(block_num, false).await
360 }
361
362 pub async fn get_block(&self, block_num: u32) -> Result<Option<SignedBlock>> {
363 self.call("get_block", json!([block_num])).await
364 }
365
366 pub async fn get_block_header(&self, block_num: u32) -> Result<Option<BlockHeader>> {
367 self.call("get_block_header", json!([block_num])).await
368 }
369}
370
371#[cfg(test)]
372mod tests {
373 use std::sync::Arc;
374 use std::time::Duration;
375
376 use serde_json::json;
377 use wiremock::matchers::{body_partial_json, method};
378 use wiremock::{Mock, MockServer, ResponseTemplate};
379
380 use crate::api::DatabaseApi;
381 use crate::client::{ClientInner, ClientOptions};
382 use crate::transport::{BackoffStrategy, FailoverTransport};
383 use crate::types::{DiscussionQuery, DiscussionQueryCategory};
384
385 #[tokio::test]
386 async fn get_accounts_calls_condenser_api() {
387 let server = MockServer::start().await;
388 Mock::given(method("POST"))
389 .and(body_partial_json(json!({
390 "method": "call",
391 "params": ["condenser_api", "get_accounts", [["alice"]]]
392 })))
393 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
394 "id": 0,
395 "jsonrpc": "2.0",
396 "result": [{"name": "alice"}]
397 })))
398 .mount(&server)
399 .await;
400
401 let transport = Arc::new(
402 FailoverTransport::new(
403 &[server.uri()],
404 Duration::from_secs(2),
405 1,
406 BackoffStrategy::default(),
407 )
408 .expect("transport should initialize"),
409 );
410 let inner = Arc::new(ClientInner::new(transport, ClientOptions::default()));
411 let api = DatabaseApi::new(inner);
412
413 let accounts = api.get_accounts(&["alice"]).await.expect("rpc should pass");
414 assert_eq!(accounts.len(), 1);
415 assert_eq!(accounts[0].name, "alice");
416 }
417
418 #[tokio::test]
419 async fn get_discussions_maps_category_to_method_name() {
420 let server = MockServer::start().await;
421 Mock::given(method("POST"))
422 .and(body_partial_json(json!({
423 "method": "call",
424 "params": ["condenser_api", "get_discussions_by_created"]
425 })))
426 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
427 "id": 0,
428 "jsonrpc": "2.0",
429 "result": []
430 })))
431 .mount(&server)
432 .await;
433
434 let transport = Arc::new(
435 FailoverTransport::new(
436 &[server.uri()],
437 Duration::from_secs(2),
438 1,
439 BackoffStrategy::default(),
440 )
441 .expect("transport should initialize"),
442 );
443 let inner = Arc::new(ClientInner::new(transport, ClientOptions::default()));
444 let api = DatabaseApi::new(inner);
445
446 let query = DiscussionQuery::default();
447 let posts = api
448 .get_discussions(DiscussionQueryCategory::Created, &query)
449 .await
450 .expect("rpc should pass");
451 assert!(posts.is_empty());
452 }
453}