Skip to main content

hive_rs/api/
database.rs

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}