photon_indexer/api/method/
get_multiple_compressed_accounts.rs1use std::collections::HashMap;
2
3use crate::{common::typedefs::account::Account, dao::generated::accounts};
4use sea_orm::{ColumnTrait, DatabaseConnection, EntityTrait, QueryFilter};
5use serde::{Deserialize, Serialize};
6use utoipa::{
7 openapi::{RefOr, Schema},
8 ToSchema,
9};
10
11use super::{
12 super::error::PhotonApiError,
13 utils::{Context, PAGE_LIMIT},
14};
15use crate::common::typedefs::hash::Hash;
16use crate::common::typedefs::serializable_pubkey::SerializablePubkey;
17
18use super::utils::parse_account_model;
19
20#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ToSchema, Default)]
21#[serde(deny_unknown_fields, rename_all = "camelCase")]
22pub struct GetMultipleCompressedAccountsRequest {
23 #[serde(default)]
24 pub hashes: Option<Vec<Hash>>,
25 #[serde(default)]
26 pub addresses: Option<Vec<SerializablePubkey>>,
27}
28
29impl GetMultipleCompressedAccountsRequest {
30 pub fn adjusted_schema() -> RefOr<Schema> {
31 let mut schema = GetMultipleCompressedAccountsRequest::schema().1;
32 let object = match schema {
33 RefOr::T(Schema::Object(ref mut object)) => {
34 let example = serde_json::to_value(GetMultipleCompressedAccountsRequest {
35 hashes: Some(vec![Hash::new_unique(), Hash::new_unique()]),
36 addresses: None,
37 })
38 .unwrap();
39 object.default = Some(example.clone());
40 object.example = Some(example);
41 object.description = Some("Request for compressed account data".to_string());
42 object.clone()
43 }
44 _ => unimplemented!(),
45 };
46 RefOr::T(Schema::Object(object))
47 }
48}
49
50#[derive(Debug, Clone, PartialEq, Eq, Serialize, ToSchema, Default)]
51#[serde(deny_unknown_fields, rename_all = "camelCase")]
52pub struct AccountList {
53 pub items: Vec<Option<Account>>,
54}
55
56#[derive(Debug, Clone, PartialEq, Eq, Serialize, ToSchema)]
57#[serde(deny_unknown_fields, rename_all = "camelCase")]
59pub struct GetMultipleCompressedAccountsResponse {
60 pub context: Context,
61 pub value: AccountList,
62}
63
64pub async fn fetch_accounts_from_hashes(
65 conn: &DatabaseConnection,
66 hashes: Vec<Hash>,
67 spent: bool,
68) -> Result<Vec<Option<accounts::Model>>, PhotonApiError> {
69 let raw_hashes: Vec<Vec<u8>> = hashes.into_iter().map(|hash| hash.to_vec()).collect();
70
71 let accounts = accounts::Entity::find()
72 .filter(
73 accounts::Column::Hash
74 .is_in(raw_hashes.clone())
75 .and(accounts::Column::Spent.eq(spent)),
76 )
77 .all(conn)
78 .await
79 .map_err(|e| PhotonApiError::UnexpectedError(format!("DB error: {}", e)))?;
80
81 let hash_to_account: HashMap<Vec<u8>, accounts::Model> = accounts
82 .into_iter()
83 .map(|account| (account.hash.clone(), account))
84 .collect();
85
86 Ok(raw_hashes
87 .into_iter()
88 .map(|hash| hash_to_account.get(&hash).cloned())
89 .collect())
90}
91
92async fn fetch_account_from_addresses(
93 conn: &DatabaseConnection,
94 addresses: Vec<SerializablePubkey>,
95) -> Result<Vec<Option<accounts::Model>>, PhotonApiError> {
96 let raw_addresses: Vec<Vec<u8>> = addresses.into_iter().map(|addr| addr.into()).collect();
97 let accounts = accounts::Entity::find()
98 .filter(
99 accounts::Column::Address
100 .is_in(raw_addresses.clone())
101 .and(accounts::Column::Spent.eq(false)),
102 )
103 .all(conn)
104 .await
105 .map_err(|e| PhotonApiError::UnexpectedError(format!("DB error: {}", e)))?;
106 let address_to_account: HashMap<Option<Vec<u8>>, accounts::Model> = accounts
107 .into_iter()
108 .map(|account| (account.address.clone(), account))
109 .collect();
110 Ok(raw_addresses
111 .into_iter()
112 .map(|addr| address_to_account.get(&Some(addr)).cloned())
113 .collect())
114}
115
116pub async fn get_multiple_compressed_accounts(
117 conn: &DatabaseConnection,
118 request: GetMultipleCompressedAccountsRequest,
119) -> Result<GetMultipleCompressedAccountsResponse, PhotonApiError> {
120 let context = Context::extract(conn).await?;
121
122 let accounts = match (request.hashes, request.addresses) {
123 (Some(hashes), None) => {
124 if hashes.len() > PAGE_LIMIT as usize {
125 return Err(PhotonApiError::ValidationError(format!(
126 "Too many hashes requested {}. Maximum allowed: {}",
127 hashes.len(),
128 PAGE_LIMIT
129 )));
130 }
131 fetch_accounts_from_hashes(conn, hashes, false).await?
132 }
133 (None, Some(addresses)) => {
134 if addresses.len() > PAGE_LIMIT as usize {
135 return Err(PhotonApiError::ValidationError(format!(
136 "Too many addresses requested {}. Maximum allowed: {}",
137 addresses.len(),
138 PAGE_LIMIT
139 )));
140 }
141 fetch_account_from_addresses(conn, addresses).await?
142 }
143 _ => panic!("Either hashes or addresses must be provided"),
144 };
145
146 Ok(GetMultipleCompressedAccountsResponse {
147 context,
148 value: AccountList {
149 items: accounts
150 .into_iter()
151 .map(|x| x.map(parse_account_model).transpose())
152 .collect::<Result<Vec<_>, _>>()?,
153 },
154 })
155}