use async_trait::async_trait;
use bson::to_document;
use bson::Document;
use debot_utils::HasId;
use futures::stream::TryStreamExt;
use mongodb::bson::doc;
use mongodb::options::*;
use mongodb::Database;
use mongodb::{Collection, IndexModel};
use serde::de::DeserializeOwned;
use serde::Serialize;
use std::error;
use std::io::{Error, ErrorKind};
use crate::PositionLog;
use super::AppState;
use super::PnlLog;
use super::PriceLog;
pub enum SearchMode {
Ascending,
Descending,
ById,
}
#[async_trait]
pub trait Entity {
async fn insert(&self, db: &Database) -> Result<(), Box<dyn error::Error>>;
async fn update(&self, db: &Database) -> Result<(), Box<dyn error::Error>>;
async fn delete(&self, db: &Database) -> Result<(), Box<dyn error::Error>>;
async fn delete_all(&self, db: &Database) -> Result<(), Box<dyn error::Error>>;
async fn search(
&self,
db: &Database,
mode: SearchMode,
limit: Option<u32>,
id: Option<u32>,
sort_key: Option<&str>,
) -> Result<Vec<Self>, Box<dyn error::Error>>
where
Self: std::marker::Sized;
fn get_collection_name(&self) -> &str;
fn get_collection(&self, db: &Database) -> Collection<Self>
where
Self: std::marker::Sized,
{
db.collection::<Self>(self.get_collection_name())
}
async fn create_indexes(&self, db: &Database) -> Result<(), Box<dyn error::Error>>
where
Self: std::marker::Sized,
Self: std::marker::Send;
}
pub async fn insert_item<T: Entity>(db: &Database, item: &T) -> Result<(), Box<dyn error::Error>> {
item.insert(db).await
}
pub async fn update_item<T: Entity>(db: &Database, item: &T) -> Result<(), Box<dyn error::Error>> {
item.update(db).await
}
#[allow(dead_code)]
pub async fn delete_item<T: Entity>(db: &Database, item: &T) -> Result<(), Box<dyn error::Error>> {
item.delete(db).await
}
#[allow(dead_code)]
pub async fn delete_item_all<T: Entity>(
db: &Database,
item: &T,
) -> Result<(), Box<dyn error::Error>> {
item.delete_all(db).await
}
pub async fn search_items<T: Entity>(
db: &Database,
item: &T,
mode: SearchMode,
limit: Option<u32>,
id: Option<u32>,
sort_key: Option<&str>,
) -> Result<Vec<T>, Box<dyn error::Error>> {
item.search(db, mode, limit, id, sort_key).await
}
pub async fn search_item<T: Entity>(
db: &Database,
item: &T,
id: Option<u32>,
sort_key: Option<&str>,
) -> Result<T, Box<dyn error::Error>> {
let mut items = item
.search(db, SearchMode::ById, None, id, sort_key)
.await?;
if items.len() == 1 {
Ok(items.pop().unwrap())
} else {
Err(Box::new(Error::new(
ErrorKind::Other,
"Multiple items are found".to_string(),
)))
}
}
async fn ensure_collection_exists(collection: &Collection<Document>) {
let doc = doc! { "_id": bson::oid::ObjectId::new() };
let _ = collection.insert_one(doc, None).await.ok();
}
async fn get_existing_indexes<T>(
collection: &Collection<T>,
) -> Result<Vec<String>, Box<dyn error::Error>> {
let mut indexes = collection.list_indexes(None).await?;
let mut index_names = Vec::new();
while let Some(index) = indexes.try_next().await? {
let document: Document = to_document(&index)?;
if let Some(name) = document.get_str("name").ok() {
index_names.push(name.to_string());
}
}
log::debug!("Existing indexes: {:?}", index_names);
Ok(index_names)
}
pub async fn create_unique_index(db: &Database) -> Result<(), Box<dyn error::Error>> {
async fn create_index<T: Entity>(
db: &Database,
entity: &T,
) -> Result<(), Box<dyn error::Error>> {
let collection: Collection<Document> =
db.collection::<Document>(entity.get_collection_name());
ensure_collection_exists(&collection).await;
let existing_indexes = get_existing_indexes(&collection).await?;
let indexes = vec![
(
"id_1",
IndexModel::builder()
.keys(doc! {"id": 1})
.options(IndexOptions::builder().unique(true).build())
.build(),
),
(
"open_timestamp_1",
IndexModel::builder()
.keys(doc! {"open_timestamp": 1})
.build(),
),
(
"open_timestamp_-1",
IndexModel::builder()
.keys(doc! {"open_timestamp": -1})
.build(),
),
(
"price_point.timestamp_1",
IndexModel::builder()
.keys(doc! {"price_point.timestamp": 1})
.build(),
),
(
"price_point.timestamp_-1",
IndexModel::builder()
.keys(doc! {"price_point.timestamp": -1})
.build(),
),
];
for (index_name, index_model) in indexes {
if !existing_indexes.contains(&index_name.to_string()) {
log::info!("Creating index `{}`...", index_name);
collection.create_index(index_model, None).await?;
log::info!("Index `{}` has been created successfully!", index_name);
} else {
log::debug!("Index `{}` already exists, skipping.", index_name);
}
}
Ok(())
}
create_index(db, &PositionLog::default()).await?;
create_index(db, &AppState::default()).await?;
create_index(db, &PriceLog::default()).await?;
create_index(db, &PnlLog::default()).await?;
Ok(())
}
#[async_trait]
impl Entity for PositionLog {
async fn create_indexes(&self, db: &Database) -> Result<(), Box<dyn error::Error>> {
let collection = self.get_collection(db);
let id_index = IndexModel::builder()
.keys(doc! {"id": 1})
.options(IndexOptions::builder().unique(true).build())
.build();
let open_timestamp_index = IndexModel::builder()
.keys(doc! {"open_timestamp": 1})
.build();
let open_timestamp_index_2 = IndexModel::builder()
.keys(doc! {"open_timestamp": -1})
.build();
collection.create_index(id_index, None).await?;
collection.create_index(open_timestamp_index, None).await?;
collection
.create_index(open_timestamp_index_2, None)
.await?;
Ok(())
}
async fn insert(&self, db: &Database) -> Result<(), Box<dyn error::Error>> {
let collection = self.get_collection(db);
collection.insert_one(self, None).await?;
Ok(())
}
async fn update(&self, db: &Database) -> Result<(), Box<dyn error::Error>> {
let query = doc! { "id": self.id() };
let update = bson::to_bson(self).unwrap();
let update = doc! { "$set" : update };
let collection = self.get_collection(db);
collection.update(query, update, true).await
}
async fn delete(&self, _db: &Database) -> Result<(), Box<dyn error::Error>> {
panic!("Not implemented")
}
async fn delete_all(&self, db: &Database) -> Result<(), Box<dyn error::Error>> {
let collection = self.get_collection(db);
collection.delete_all().await
}
async fn search(
&self,
db: &Database,
mode: SearchMode,
limit: Option<u32>,
id: Option<u32>,
sort_key: Option<&str>,
) -> Result<Vec<Self>, Box<dyn error::Error>> {
let mut query = doc! { "id": { "$gt": 0 }};
if self.id() != None {
query = doc! { "id": self.id().unwrap() };
}
let collection = self.get_collection(db);
let sort_key = sort_key.unwrap_or("id");
collection.search(query, mode, limit, id, &sort_key).await
}
fn get_collection_name(&self) -> &str {
"position"
}
}
#[async_trait]
impl Entity for PnlLog {
async fn create_indexes(&self, db: &Database) -> Result<(), Box<dyn error::Error>> {
let collection = self.get_collection(db);
let id_index = IndexModel::builder()
.keys(doc! {"id": 1})
.options(IndexOptions::builder().unique(true).build())
.build();
collection.create_index(id_index, None).await?;
Ok(())
}
async fn insert(&self, db: &Database) -> Result<(), Box<dyn error::Error>> {
let collection = self.get_collection(db);
collection.insert_one(self, None).await?;
Ok(())
}
async fn update(&self, _db: &Database) -> Result<(), Box<dyn error::Error>> {
panic!("Not implemented")
}
async fn delete(&self, _db: &Database) -> Result<(), Box<dyn error::Error>> {
panic!("Not implemented")
}
async fn delete_all(&self, _db: &Database) -> Result<(), Box<dyn error::Error>> {
panic!("Not implemented")
}
async fn search(
&self,
db: &Database,
mode: SearchMode,
limit: Option<u32>,
id: Option<u32>,
sort_key: Option<&str>,
) -> Result<Vec<Self>, Box<dyn error::Error>> {
let mut query = doc! { "id": { "$gt": 0 }};
if self.id != None {
query = doc! { "id": self.id.unwrap() };
}
let collection = self.get_collection(db);
let sort_key = sort_key.unwrap_or("id");
collection.search(query, mode, limit, id, &sort_key).await
}
fn get_collection_name(&self) -> &str {
"balance"
}
}
#[async_trait]
impl Entity for AppState {
async fn create_indexes(&self, db: &Database) -> Result<(), Box<dyn error::Error>> {
let collection = self.get_collection(db);
let id_index = IndexModel::builder()
.keys(doc! {"id": 1})
.options(IndexOptions::builder().unique(true).build())
.build();
collection.create_index(id_index, None).await?;
Ok(())
}
async fn insert(&self, _db: &Database) -> Result<(), Box<dyn error::Error>> {
panic!("Not implemented")
}
async fn update(&self, db: &Database) -> Result<(), Box<dyn error::Error>> {
let query = doc! { "id": 1 };
let update = bson::to_bson(self).unwrap();
let update = doc! { "$set" : update };
let collection = self.get_collection(db);
collection.update(query, update, true).await
}
async fn delete(&self, _db: &Database) -> Result<(), Box<dyn error::Error>> {
panic!("Not implemented")
}
async fn delete_all(&self, db: &Database) -> Result<(), Box<dyn error::Error>> {
let collection = self.get_collection(db);
collection.delete_all().await
}
async fn search(
&self,
db: &Database,
mode: SearchMode,
limit: Option<u32>,
id: Option<u32>,
sort_key: Option<&str>,
) -> Result<Vec<Self>, Box<dyn error::Error>> {
let query = doc! { "id": 1 };
let collection = self.get_collection(db);
let sort_key = sort_key.unwrap_or("id");
collection.search(query, mode, limit, id, &sort_key).await
}
fn get_collection_name(&self) -> &str {
"app-state"
}
}
#[async_trait]
impl Entity for PriceLog {
async fn create_indexes(&self, db: &Database) -> Result<(), Box<dyn error::Error>> {
let collection = self.get_collection(db);
let id_index = IndexModel::builder()
.keys(doc! {"id": 1})
.options(IndexOptions::builder().unique(true).build())
.build();
let price_point_timestamp_index = IndexModel::builder()
.keys(doc! {"price_point.timestamp": 1})
.build();
let price_point_timestamp_index_2 = IndexModel::builder()
.keys(doc! {"price_point.timestamp": -1})
.build();
collection.create_index(id_index, None).await?;
collection
.create_index(price_point_timestamp_index, None)
.await?;
collection
.create_index(price_point_timestamp_index_2, None)
.await?;
Ok(())
}
async fn insert(&self, db: &Database) -> Result<(), Box<dyn error::Error>> {
let collection = self.get_collection(db);
collection.insert_one(self, None).await?;
Ok(())
}
async fn update(&self, db: &Database) -> Result<(), Box<dyn error::Error>> {
let query = doc! { "id": self.id };
let update = bson::to_bson(self).unwrap();
let update = doc! { "$set" : update };
let collection = self.get_collection(db);
collection.update(query, update, true).await
}
async fn delete(&self, _db: &Database) -> Result<(), Box<dyn error::Error>> {
panic!("Not implemented")
}
async fn delete_all(&self, _db: &Database) -> Result<(), Box<dyn error::Error>> {
panic!("Not implemented")
}
async fn search(
&self,
db: &Database,
mode: SearchMode,
limit: Option<u32>,
id: Option<u32>,
sort_key: Option<&str>,
) -> Result<Vec<Self>, Box<dyn error::Error>> {
let mut query = doc! { "id": { "$gt": 0 }};
if self.id != None {
query = doc! { "id": self.id.unwrap() };
}
let collection = self.get_collection(db);
let sort_key = sort_key.unwrap_or("id");
collection.search(query, mode, limit, id, &sort_key).await
}
fn get_collection_name(&self) -> &str {
"price"
}
}
#[async_trait]
pub trait HelperCollection<T> {
async fn update(
&self,
query: Document,
update: Document,
upsert: bool,
) -> Result<(), Box<dyn error::Error>>;
async fn delete(&self, query: Document) -> Result<(), Box<dyn error::Error>>;
async fn delete_all(&self) -> Result<(), Box<dyn error::Error>>;
async fn search(
&self,
query: Document,
mode: SearchMode,
limit: Option<u32>,
id: Option<u32>,
sort_key: &str,
) -> Result<Vec<T>, Box<dyn error::Error>>;
}
#[async_trait]
impl<T> HelperCollection<T> for Collection<T>
where
T: DeserializeOwned + Unpin + Send + Sync + Serialize + std::fmt::Debug,
{
async fn update(
&self,
query: Document,
update: Document,
upsert: bool,
) -> Result<(), Box<dyn error::Error>> {
let options = FindOneAndUpdateOptions::builder()
.upsert(upsert)
.return_document(ReturnDocument::After)
.build();
let _ = self.find_one_and_update(query, update, options).await?;
Ok(())
}
async fn delete(&self, query: Document) -> Result<(), Box<dyn error::Error>> {
let result = self.delete_one(query, None).await?;
if result.deleted_count == 1 {
return Ok(());
} else {
panic!("Not implemented")
}
}
async fn delete_all(&self) -> Result<(), Box<dyn error::Error>> {
let options = DropCollectionOptions::builder().build();
self.drop(options).await?;
Ok(())
}
async fn search(
&self,
mut query: Document,
mode: SearchMode,
limit: Option<u32>,
id: Option<u32>,
sort_key: &str,
) -> Result<Vec<T>, Box<dyn error::Error>> {
let mut items: Vec<T> = vec![];
match sort_key {
"id" | "open_timestamp" | "price_point.timestamp" => {}
_ => {
return Err(Box::new(Error::new(
ErrorKind::InvalidInput,
"Invalid sort key",
)))
}
};
let find_options = match mode {
SearchMode::Ascending => {
let builder = FindOptions::builder()
.allow_disk_use(Some(true))
.sort(doc! { sort_key: 1 });
if let Some(limit_value) = limit {
builder.limit(limit_value as i64).build()
} else {
builder.build()
}
}
SearchMode::Descending => {
let builder = FindOptions::builder()
.allow_disk_use(Some(true))
.sort(doc! { sort_key: -1 });
if let Some(limit_value) = limit {
builder.limit(limit_value as i64).build()
} else {
builder.build()
}
}
SearchMode::ById => {
if let Some(id_value) = id {
query.insert("id", id_value);
} else {
return Err(Box::new(Error::new(
ErrorKind::InvalidInput,
"ID not provided".to_string(),
)));
}
FindOptions::builder().allow_disk_use(Some(true)).build()
}
};
let mut cursor = self.find(query, find_options).await?;
while let Some(item) = cursor.try_next().await? {
items.push(item);
}
if items.is_empty() {
Err(Box::new(Error::new(
ErrorKind::Other,
"Item not found".to_string(),
)))
} else {
Ok(items)
}
}
}