main.rs 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  1. use actix_web::{
  2. error::InternalError, get, middleware::Logger, post, web, App, HttpResponse, HttpServer,
  3. Responder,
  4. };
  5. use serde::{Deserialize, Serialize};
  6. use serde_json::json;
  7. use std::sync::Arc;
  8. use subscribe::subscribe_by_tag;
  9. use verax::{AccountId, AnyAmount, AnyId, Asset, Filter, RevId, Status, Tag, Type};
  10. mod subscribe;
  11. #[derive(Deserialize)]
  12. pub struct Movement {
  13. pub account: AccountId,
  14. #[serde(flatten)]
  15. pub amount: AnyAmount,
  16. }
  17. #[derive(Deserialize)]
  18. pub struct Deposit {
  19. pub account: AccountId,
  20. #[serde(flatten)]
  21. pub amount: AnyAmount,
  22. pub memo: String,
  23. pub tags: Vec<Tag>,
  24. pub status: Status,
  25. }
  26. impl Deposit {
  27. pub async fn to_ledger_transaction(
  28. self,
  29. ledger: &Ledger,
  30. ) -> Result<verax::Transaction, verax::Error> {
  31. let zdeposit = ledger
  32. ._inner
  33. .deposit(
  34. &self.account,
  35. self.amount.try_into()?,
  36. self.status,
  37. vec![],
  38. self.memo,
  39. )
  40. .await?;
  41. Ok(if !self.tags.is_empty() {
  42. ledger
  43. ._inner
  44. .set_tags(zdeposit.revision_id, self.tags, "Update tags".to_owned())
  45. .await?
  46. } else {
  47. zdeposit
  48. })
  49. }
  50. }
  51. #[derive(Deserialize)]
  52. pub struct Transaction {
  53. pub debit: Vec<Movement>,
  54. pub credit: Vec<Movement>,
  55. pub memo: String,
  56. pub status: Status,
  57. }
  58. #[derive(Deserialize)]
  59. pub struct UpdateTransaction {
  60. pub status: Status,
  61. pub memo: String,
  62. }
  63. impl UpdateTransaction {
  64. pub async fn to_ledger_transaction(
  65. self,
  66. id: RevId,
  67. ledger: &Ledger,
  68. ) -> Result<verax::Transaction, verax::Error> {
  69. ledger
  70. ._inner
  71. .change_status(id, self.status, self.memo)
  72. .await
  73. }
  74. }
  75. impl Transaction {
  76. pub async fn to_ledger_transaction(
  77. self,
  78. ledger: &Ledger,
  79. ) -> Result<verax::Transaction, verax::Error> {
  80. let from = self
  81. .debit
  82. .into_iter()
  83. .map(|x| x.amount.try_into().map(|amount| (x.account, amount)))
  84. .collect::<Result<Vec<_>, _>>()?;
  85. let to = self
  86. .credit
  87. .into_iter()
  88. .map(|x| x.amount.try_into().map(|amount| (x.account, amount)))
  89. .collect::<Result<Vec<_>, _>>()?;
  90. ledger
  91. ._inner
  92. .new_transaction(self.memo, self.status, from, to)
  93. .await
  94. }
  95. }
  96. #[derive(Serialize)]
  97. struct AccountResponse {
  98. amount: String,
  99. cents: String,
  100. asset: Asset,
  101. }
  102. #[get("/balance/{id}")]
  103. async fn get_balance(info: web::Path<AccountId>, ctx: web::Data<Ledger>) -> impl Responder {
  104. match ctx._inner.get_balance(&info.0).await {
  105. Ok(balances) => HttpResponse::Ok().json(
  106. balances
  107. .into_iter()
  108. .map(|amount| AccountResponse {
  109. amount: amount.to_string(),
  110. cents: amount.cents().to_string(),
  111. asset: amount.asset().clone(),
  112. })
  113. .collect::<Vec<_>>(),
  114. ),
  115. Err(err) => HttpResponse::BadRequest().json(json!({ "text": err.to_string(), "err": err})),
  116. }
  117. }
  118. #[get("/{id}")]
  119. async fn get_info(info: web::Path<AnyId>, ctx: web::Data<Ledger>) -> impl Responder {
  120. let (cache_for_ever, filter) = match info.0 {
  121. AnyId::Account(account_id) => (
  122. false,
  123. Filter {
  124. accounts: vec![account_id],
  125. typ: vec![Type::Deposit, Type::Withdrawal, Type::Transaction],
  126. ..Default::default()
  127. },
  128. ),
  129. AnyId::Revision(rev_id) => (
  130. true,
  131. Filter {
  132. revisions: vec![rev_id],
  133. limit: 1,
  134. ..Default::default()
  135. },
  136. ),
  137. AnyId::Transaction(transaction_id) => (
  138. false,
  139. Filter {
  140. ids: vec![transaction_id],
  141. limit: 1,
  142. ..Default::default()
  143. },
  144. ),
  145. AnyId::Payment(payment_id) => {
  146. let _ = ctx
  147. ._inner
  148. .get_payment_info(&payment_id)
  149. .await
  150. .map(|tx| HttpResponse::Ok().json(tx));
  151. todo!()
  152. }
  153. };
  154. let limit = filter.limit;
  155. ctx._inner
  156. .get_transactions(filter)
  157. .await
  158. .map(|results| {
  159. let json_response = if limit == 1 {
  160. serde_json::to_value(&results[0])
  161. } else {
  162. serde_json::to_value(&results)
  163. }
  164. .unwrap();
  165. if cache_for_ever {
  166. HttpResponse::Ok()
  167. .header(
  168. "Cache-Control",
  169. "public, max-age=31536000, s-maxage=31536000, immutable",
  170. )
  171. .header("Vary", "Accept-Encoding")
  172. .json(json_response)
  173. } else {
  174. HttpResponse::Ok().json(json_response)
  175. }
  176. })
  177. .map_err(|err| {
  178. HttpResponse::InternalServerError().json(json!({ "text": err.to_string(), "err": err}))
  179. })
  180. }
  181. #[post("/deposit")]
  182. async fn deposit(item: web::Json<Deposit>, ledger: web::Data<Ledger>) -> impl Responder {
  183. // Insert the item into a database or another data source.
  184. // For this example, we'll just echo the received item.
  185. match item.into_inner().to_ledger_transaction(&ledger).await {
  186. Ok(tx) => {
  187. // Insert the item into a database or another data source.
  188. // For this example, we'll just echo the received item.
  189. HttpResponse::Created().json(tx)
  190. }
  191. Err(err) => HttpResponse::BadRequest().json(json!({ "text": err.to_string(), "err": err})),
  192. }
  193. }
  194. #[post("/tx")]
  195. async fn create_transaction(
  196. item: web::Json<Transaction>,
  197. ledger: web::Data<Ledger>,
  198. ) -> impl Responder {
  199. match item.into_inner().to_ledger_transaction(&ledger).await {
  200. Ok(tx) => HttpResponse::Accepted().json(tx),
  201. Err(err) => {
  202. HttpResponse::InternalServerError().json(json!({ "text": err.to_string(), "err": err}))
  203. }
  204. }
  205. }
  206. #[post("/{id}")]
  207. async fn update_status(
  208. info: web::Path<RevId>,
  209. item: web::Json<UpdateTransaction>,
  210. ctx: web::Data<Ledger>,
  211. ) -> impl Responder {
  212. match item.into_inner().to_ledger_transaction(info.0, &ctx).await {
  213. Ok(tx) => HttpResponse::Accepted().json(tx),
  214. Err(err) => {
  215. HttpResponse::InternalServerError().json(json!({ "text": err.to_string(), "err": err}))
  216. }
  217. }
  218. }
  219. pub struct Ledger {
  220. _inner: Arc<verax::Ledger<verax::storage::Cache<verax::storage::SQLite>>>,
  221. }
  222. #[actix_web::main]
  223. async fn main() -> std::io::Result<()> {
  224. if std::env::var_os("RUST_LOG").is_none() {
  225. std::env::set_var("RUST_LOG", "actix_web=info");
  226. }
  227. env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
  228. let settings = "sqlite:./test.db"
  229. .parse::<verax::storage::sqlite::SqliteConnectOptions>()
  230. .expect("valid settings")
  231. .journal_mode(verax::storage::sqlite::SqliteJournalMode::Wal)
  232. .create_if_missing(true);
  233. let pool = verax::storage::sqlite::SqlitePoolOptions::new()
  234. .connect_with(settings)
  235. .await
  236. .expect("pool");
  237. let storage = verax::storage::SQLite::new(pool.clone());
  238. storage.setup().await.expect("setup");
  239. let storage = verax::storage::SQLite::new(pool.clone());
  240. let storage = verax::storage::Cache::new(storage);
  241. let ledger = verax::Ledger::new(storage.into());
  242. HttpServer::new(move || {
  243. let ledger = ledger.clone();
  244. App::new()
  245. .wrap(Logger::default())
  246. .app_data(web::Data::new(Ledger { _inner: ledger }))
  247. .app_data(web::JsonConfig::default().error_handler(|err, _req| {
  248. InternalError::from_response(
  249. "",
  250. HttpResponse::BadRequest()
  251. .content_type("application/json")
  252. .body(format!(r#"{{"error":"{}"}}"#, err)),
  253. )
  254. .into()
  255. }))
  256. .service(subscribe_by_tag)
  257. .service(get_balance)
  258. .service(get_info)
  259. .service(deposit)
  260. .service(create_transaction)
  261. .service(update_status)
  262. })
  263. .bind("127.0.0.1:8080")?
  264. .run()
  265. .await
  266. }