pub mod application; pub mod bots; pub mod callback_info; pub mod message_forward; pub mod raw_calls; use std::time::Duration; use async_trait::async_trait; use chrono::{DateTime, Local, Utc}; use enum_stringify::EnumStringify; use futures::stream::TryStreamExt; use mongodb::bson::serde_helpers::chrono_datetime_as_bson_datetime; use mongodb::options::IndexOptions; use mongodb::{bson::doc, options::ClientOptions, Client}; use mongodb::{Collection, Database, IndexModel}; use serde::{Deserialize, Serialize}; #[derive(EnumStringify)] #[enum_stringify(case = "flat")] pub enum ReservationStatus { Booked, Paid, } pub trait GetReservationStatus { fn get_status(&self) -> Option; } //impl GetReservationStatus for models::Reservation { // fn get_status(&self) -> Option { // ReservationStatus::try_from(self.status.clone()).ok() // } //} #[derive(Serialize, Deserialize, Default)] pub struct User { pub _id: bson::oid::ObjectId, pub id: i64, pub is_admin: bool, pub first_name: String, pub last_name: Option, pub username: Option, pub language_code: Option, pub metas: Vec, } #[macro_export] macro_rules! query_call { ($func_name:ident, $self:ident, $db:ident, $return_type:ty, $body:block) => { pub async fn $func_name(&$self, $db: &mut D) -> DbResult<$return_type> $body }; } #[macro_export] macro_rules! query_call_consume { ($func_name:ident, $self:ident, $db:ident, $return_type:ty, $body:block) => { pub async fn $func_name($self, $db: &mut D) -> DbResult<$return_type> $body }; } impl User { query_call!(update_user, self, db, (), { let db_collection = db.get_database().await.collection::("users"); db_collection .update_one( doc! { "_id": self._id }, doc! { "$set": { "first_name": &self.first_name, "last_name": &self.last_name, "username": &self.username, "language_code": &self.language_code, "is_admin": &self.is_admin, } }, ) .await?; Ok(()) }); pub async fn insert_meta(&self, db: &mut D, meta: &str) -> DbResult<()> { let db_collection = db.get_database().await.collection::("users"); db_collection .update_one( doc! { "_id": self._id }, doc! { "$push": { "metas": meta, } }, ) .await?; Ok(()) } } #[derive(Serialize, Deserialize)] pub struct Message { pub _id: bson::oid::ObjectId, pub chat_id: i64, pub message_id: i64, pub token: String, pub variant: Option, #[serde(with = "chrono_datetime_as_bson_datetime")] pub created_at: DateTime, } #[derive(Serialize, Deserialize)] pub struct Literal { pub _id: bson::oid::ObjectId, pub token: String, pub value: String, } #[derive(Serialize, Deserialize)] pub struct LiteralAlternative { pub _id: bson::oid::ObjectId, pub token: String, pub variant: String, pub value: String, } #[derive(Serialize, Deserialize)] pub struct Event { pub _id: bson::oid::ObjectId, pub time: DateTime, } #[derive(Serialize, Deserialize)] pub struct Media { pub _id: bson::oid::ObjectId, pub token: String, pub media_type: String, pub file_id: String, pub media_group_id: Option, } #[derive(Clone)] pub struct DB { client: Client, name: String, } impl DB { pub async fn new>(db_url: S, name: String) -> DbResult { let options = ClientOptions::parse(db_url.into()).await?; let client = Client::with_options(options)?; Ok(DB { client, name }) } pub async fn migrate(&mut self) -> DbResult<()> { /// some migrations doesn't realy need type of collection type AnyCollection = Event; let events = self.get_database().await.collection::("events"); events .create_index( IndexModel::builder() .keys(doc! {"time": 1}) .options(IndexOptions::builder().unique(true).build()) .build(), ) .await?; // clear callbacks after a day because otherwise database will contain so much data // for just button clicks let callback_info = self .get_database() .await .collection::("callback_info"); callback_info .create_index( IndexModel::builder() .keys(doc! {"created_at": 1}) .options( IndexOptions::builder() .expire_after(Duration::from_secs(60 * 60 * 24 /* 1 day */)) .build(), ) .build(), ) .await?; Ok(()) } pub async fn init>(db_url: S, name: String) -> DbResult { let mut db = Self::new(db_url, name).await?; db.migrate().await?; Ok(db) } pub fn with_name(self, name: String) -> Self { Self { name, ..self } } } pub trait DbCollection { const COLLECTION: &str; } #[async_trait] pub trait GetCollection { async fn get_collection(&mut self) -> Collection; } #[async_trait] impl CallDB for DB { async fn get_database(&mut self) -> Database { self.client.database(&self.name) } async fn get_database_immut(&self) -> Database { self.client.database(&self.name) } } #[async_trait] impl GetCollection for T { async fn get_collection(&mut self) -> Collection { self.get_database() .await .collection(::COLLECTION) } } #[derive(thiserror::Error, Debug)] pub enum DbError { #[error("error while processing mongodb query: {0}")] MongodbError(#[from] mongodb::error::Error), #[error("error while coverting values: {0}")] SerdeJsonError(#[from] serde_json::error::Error), } pub type DbResult = Result; #[async_trait] pub trait CallDB { //type C; async fn get_database(&mut self) -> Database; async fn get_database_immut(&self) -> Database; //async fn get_pool(&mut self) -> PooledConnection<'_, AsyncDieselConnectionManager>; async fn get_users(&self) -> DbResult> { let db = self.get_database_immut().await; let users = db.collection::("users"); Ok(users.find(doc! {}).await?.try_collect().await?) } async fn get_users_by_ids(&self, ids: Vec) -> DbResult> { let db = self.get_database_immut().await; let users = db.collection::("users"); Ok(users .find(doc! {"id": {"$in": ids}}) .await? .try_collect() .await?) } async fn get_random_users(&self, n: u32) -> DbResult> { let db = self.get_database_immut().await; let users = db.collection::("users"); let random_users: Vec = users .aggregate(vec![doc! {"$sample": {"size": n}}]) .await? .try_collect() .await?; let random_users = random_users .into_iter() .map(|d| match serde_json::to_value(d) { Ok(value) => serde_json::from_value::(value), Err(err) => Err(err), }) .collect::>()?; Ok(random_users) } async fn set_admin(&mut self, userid: i64, isadmin: bool) -> DbResult<()> { let db = self.get_database().await; let users = db.collection::("users"); users .update_one( doc! { "id": userid }, doc! { "$set": { "is_admin": isadmin } }, ) .await?; Ok(()) } async fn get_or_init_user(&mut self, userid: i64, firstname: &str) -> DbResult { let db = self.get_database().await; let users = db.collection::("users"); users .update_one( doc! { "id": userid }, doc! { "$set": doc! { "first_name": firstname}, "$setOnInsert": doc! { "is_admin": false, "metas": [] }, }, ) .upsert(true) .await?; Ok(users .find_one(doc! { "id": userid }) .await? .expect("no such user created")) } async fn get_message(&mut self, chatid: i64, messageid: i32) -> DbResult> { let db = self.get_database().await; let messages = db.collection::("messages"); let msg = messages .find_one(doc! { "chat_id": chatid, "message_id": messageid as i64 }) .await?; Ok(msg) } async fn get_message_literal( &mut self, chatid: i64, messageid: i32, ) -> DbResult> { let msg = self.get_message(chatid, messageid).await?; Ok(msg.map(|m| m.token)) } async fn set_message_literal( &mut self, chatid: i64, messageid: i32, literal: &str, ) -> DbResult<()> { let db = self.get_database().await; let messages = db.collection::("messages"); messages .update_one( doc! { "chat_id": chatid, "message_id": messageid as i64 }, doc! { "$set": { "token": literal, "created_at": Into::>::into(Local::now()), } }, ) .upsert(true) .await?; Ok(()) } async fn set_message_literal_variant( &mut self, chatid: i64, messageid: i32, literal: &str, variant: &str, ) -> DbResult<()> { let db = self.get_database().await; let messages = db.collection::("messages"); messages .update_one( doc! { "chat_id": chatid, "message_id": messageid as i64 }, doc! { "$set": { "token": literal, "variant": variant, "created_at": Into::>::into(Local::now()), } }, ) .upsert(true) .await?; Ok(()) } async fn get_literal(&self, literal: &str) -> DbResult> { let db = self.get_database_immut().await; let messages = db.collection::("literals"); let literal = messages.find_one(doc! { "token": literal }).await?; Ok(literal) } async fn get_literal_value(&self, literal: &str) -> DbResult> { let literal = self.get_literal(literal).await?; Ok(literal.map(|l| l.value)) } async fn set_literal(&mut self, literal: &str, valuestr: &str) -> DbResult<()> { let db = self.get_database().await; let literals = db.collection::("literals"); literals .update_one( doc! { "token": literal }, doc! { "$set": { "value": valuestr } }, ) .upsert(true) .await?; Ok(()) } async fn get_literal_alternative( &mut self, literal: &str, variant: &str, ) -> DbResult> { let db = self.get_database().await; let messages = db.collection::("literal_alternatives"); let literal = messages .find_one(doc! { "token": literal, "variant": variant }) .await?; Ok(literal) } async fn get_literal_alternative_value( &mut self, literal: &str, variant: &str, ) -> DbResult> { let literal = self.get_literal_alternative(literal, variant).await?; Ok(literal.map(|l| l.value)) } async fn set_literal_alternative( &mut self, literal: &str, variant: &str, valuestr: &str, ) -> DbResult<()> { let db = self.get_database().await; let literals = db.collection::("literal_alternatives"); literals .update_one( doc! { "token": literal, "variant": variant }, doc! { "$set": { "value": valuestr } }, ) .upsert(true) .await?; Ok(()) } async fn get_all_events(&mut self) -> DbResult> { let db = self.get_database().await; let events = db.collection::("events"); Ok(events.find(doc! {}).await?.try_collect().await?) } async fn create_event(&mut self, event_datetime: chrono::DateTime) -> DbResult { let db = self.get_database().await; let events = db.collection::("events"); let new_event = Event { _id: bson::oid::ObjectId::new(), time: event_datetime, }; events.insert_one(&new_event).await?; Ok(new_event) } async fn delete_event(&mut self, event_datetime: chrono::DateTime) -> DbResult<()> { let db = self.get_database().await; let events = db.collection::("events"); events.delete_one(doc! { "time": event_datetime }).await?; Ok(()) } async fn delete_all_events(&mut self) -> DbResult { let db = self.get_database().await; let events = db.collection::("events"); let delete_result = events.delete_many(doc! {}).await?; Ok(delete_result.deleted_count as usize) } async fn get_media(&mut self, literal: &str) -> DbResult> { let db = self.get_database().await; let media = db.collection::("media"); let media_items = media .find(doc! { "token": literal }) .await? .try_collect() .await?; Ok(media_items) } async fn is_media_group_exists(&mut self, media_group: &str) -> DbResult { let db = self.get_database().await; let media = db.collection::("media"); let is_exists = media .count_documents(doc! { "media_group_id": media_group }) .await? > 0; Ok(is_exists) } async fn drop_media(&mut self, literal: &str) -> DbResult { let db = self.get_database().await; let media = db.collection::("media"); let deleted_count = media .delete_many(doc! { "token": literal }) .await? .deleted_count; Ok(deleted_count as usize) } async fn drop_media_except(&mut self, literal: &str, except_group: &str) -> DbResult { let db = self.get_database().await; let media = db.collection::("media"); let deleted_count = media .delete_many(doc! { "token": literal, "media_group_id": { "$ne": except_group } }) .await? .deleted_count; Ok(deleted_count as usize) } async fn add_media( &mut self, literal: &str, mediatype: &str, fileid: &str, media_group: Option<&str>, ) -> DbResult { let db = self.get_database().await; let media = db.collection::("media"); let new_media = Media { _id: bson::oid::ObjectId::new(), token: literal.to_string(), media_type: mediatype.to_string(), file_id: fileid.to_string(), media_group_id: media_group.map(|g| g.to_string()), }; media.insert_one(&new_media).await?; Ok(new_media) } } #[cfg(test)] mod tests;