|
@@ -1,14 +1,16 @@
|
|
|
use crate::{
|
|
|
amount::AmountCents,
|
|
|
asset::AssetId,
|
|
|
+ changelog::{sort_changes, Changelog},
|
|
|
storage::{Error, Storage},
|
|
|
- transaction::{from_db, Type},
|
|
|
+ transaction::{self, from_db, Type},
|
|
|
AccountId, Amount, Asset, AssetManager, Payment, PaymentId, Status, TransactionId,
|
|
|
};
|
|
|
use chrono::NaiveDateTime;
|
|
|
use futures::TryStreamExt;
|
|
|
+use serde::{de::DeserializeOwned, Serialize};
|
|
|
use sqlx::{sqlite::SqliteRow, Executor, Row};
|
|
|
-use std::{collections::HashMap, marker::PhantomData};
|
|
|
+use std::{collections::HashMap, marker::PhantomData, ops::Deref};
|
|
|
|
|
|
mod batch;
|
|
|
|
|
@@ -51,6 +53,7 @@ impl<'a> Sqlite<'a> {
|
|
|
"status" INTEGER NOT NULL,
|
|
|
"type" INTEGER NOT NULL,
|
|
|
"reference" TEXT NOT NULL,
|
|
|
+ "last_version" BLOB NOT NULL,
|
|
|
"created_at" DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
|
"updated_at" DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
|
PRIMARY KEY ("transaction_id")
|
|
@@ -72,6 +75,15 @@ impl<'a> Sqlite<'a> {
|
|
|
"updated_at" DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
|
PRIMARY KEY ("transaction_id", "payment_transaction_id", "payment_position_id")
|
|
|
);
|
|
|
+ CREATE TABLE IF NOT EXISTS "changelog" (
|
|
|
+ "id" BLOB NOT NULL,
|
|
|
+ "object_id" BLOB NOT NULL,
|
|
|
+ "previous" BLOB DEFAULT NULL,
|
|
|
+ "change" BLOB NOT NULL,
|
|
|
+ "created_at" DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
|
+ PRIMARY KEY ("id")
|
|
|
+ );
|
|
|
+ CREATE INDEX IF NOT EXISTS "changelog_object_id" ON "changelog" ("object_id", "created_at");
|
|
|
"#,
|
|
|
)
|
|
|
.await
|
|
@@ -308,6 +320,7 @@ impl<'a> Storage<'a, Batch<'a>> for Sqlite<'a> {
|
|
|
"t"."status",
|
|
|
"t"."type",
|
|
|
"t"."reference",
|
|
|
+ "t"."last_version",
|
|
|
"t"."created_at",
|
|
|
"t"."updated_at"
|
|
|
FROM
|
|
@@ -403,21 +416,34 @@ impl<'a> Storage<'a, Batch<'a>> for Sqlite<'a> {
|
|
|
.try_get::<String, usize>(2)
|
|
|
.map_err(|_| Error::Storage("Invalid reference".to_string()))?;
|
|
|
|
|
|
+ let last_change = transaction_row
|
|
|
+ .try_get::<Vec<u8>, usize>(3)
|
|
|
+ .map_err(|_| Error::Storage("Invalid last_version".to_string()))?;
|
|
|
+
|
|
|
let created_at = transaction_row
|
|
|
- .try_get::<NaiveDateTime, usize>(3)
|
|
|
+ .try_get::<NaiveDateTime, usize>(4)
|
|
|
.map_err(|e| Error::InvalidDate(e.to_string()))?
|
|
|
.and_utc();
|
|
|
|
|
|
let updated_at = transaction_row
|
|
|
- .try_get::<NaiveDateTime, usize>(4)
|
|
|
+ .try_get::<NaiveDateTime, usize>(5)
|
|
|
.map_err(|e| Error::InvalidDate(e.to_string()))?
|
|
|
.and_utc();
|
|
|
|
|
|
+ drop(transaction_row);
|
|
|
+ drop(create_result);
|
|
|
+ drop(conn);
|
|
|
+
|
|
|
+ let changes = self
|
|
|
+ .get_changelogs::<transaction::Changelog>(transaction_id.deref().to_vec())
|
|
|
+ .await?;
|
|
|
+
|
|
|
Ok(from_db::Transaction {
|
|
|
id: transaction_id.clone(),
|
|
|
spend,
|
|
|
create,
|
|
|
typ,
|
|
|
+ changelog: sort_changes(changes, last_change)?,
|
|
|
status,
|
|
|
reference,
|
|
|
created_at,
|
|
@@ -471,4 +497,43 @@ impl<'a> Storage<'a, Batch<'a>> for Sqlite<'a> {
|
|
|
|
|
|
Ok(transactions)
|
|
|
}
|
|
|
+
|
|
|
+ async fn get_changelogs<T: DeserializeOwned + Serialize + Send + Sync>(
|
|
|
+ &self,
|
|
|
+ object_id: Vec<u8>,
|
|
|
+ ) -> Result<Vec<Changelog<T>>, Error> {
|
|
|
+ let mut conn = self
|
|
|
+ .db
|
|
|
+ .acquire()
|
|
|
+ .await
|
|
|
+ .map_err(|e| Error::Storage(e.to_string()))?;
|
|
|
+
|
|
|
+ let rows = sqlx::query(
|
|
|
+ r#"SELECT "previous", "change", "created_at", "id" FROM "changelog" WHERE "object_id" = ? ORDER BY "created_at" ASC"#,
|
|
|
+ ).bind(&object_id).fetch_all(&mut *conn).await.map_err(|e| Error::Storage(e.to_string()))?;
|
|
|
+
|
|
|
+ let mut results = vec![];
|
|
|
+
|
|
|
+ for row in rows.into_iter() {
|
|
|
+ let change: T = bincode::deserialize(
|
|
|
+ &row.try_get::<Vec<u8>, usize>(1)
|
|
|
+ .map_err(|_| Error::Storage("Invalid change content".to_string()))?,
|
|
|
+ )
|
|
|
+ .map_err(|e| Error::Storage(e.to_string()))?;
|
|
|
+ let created_at = row
|
|
|
+ .try_get::<NaiveDateTime, usize>(2)
|
|
|
+ .map_err(|e| Error::InvalidDate(e.to_string()))?
|
|
|
+ .and_utc();
|
|
|
+
|
|
|
+ results.push(Changelog::new_from_db(
|
|
|
+ row.try_get::<Option<Vec<u8>>, usize>(0)
|
|
|
+ .map_err(|_| Error::Storage("Invalid previous content".to_string()))?,
|
|
|
+ object_id.clone(),
|
|
|
+ change,
|
|
|
+ created_at,
|
|
|
+ ));
|
|
|
+ }
|
|
|
+
|
|
|
+ Ok(results)
|
|
|
+ }
|
|
|
}
|