Compare commits
12 Commits
ccd38178ea
...
0c71fd3796
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0c71fd3796 | ||
|
|
5192b43e0b | ||
|
|
8c2d2425c4 | ||
|
|
36c729e57b | ||
|
|
cbe24cc134 | ||
|
|
58a16a5927 | ||
|
|
a00c0017bb | ||
|
|
48bb7b133b | ||
|
|
221fb87c8f | ||
|
|
078e2fd62a | ||
|
|
0973499652 | ||
|
|
b56f07e6be |
62
src/db/callback_info.rs
Normal file
62
src/db/callback_info.rs
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
use crate::query_call_consume;
|
||||||
|
use crate::CallDB;
|
||||||
|
use bson::oid::ObjectId;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use super::DbResult;
|
||||||
|
use bson::doc;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Default)]
|
||||||
|
pub struct CallbackInfo<C>
|
||||||
|
where
|
||||||
|
C: Serialize,
|
||||||
|
{
|
||||||
|
pub _id: bson::oid::ObjectId,
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub callback: C,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C> CallbackInfo<C>
|
||||||
|
where
|
||||||
|
C: Serialize + for<'a> Deserialize<'a> + Send + Sync,
|
||||||
|
{
|
||||||
|
pub fn new(callback: C) -> Self {
|
||||||
|
Self {
|
||||||
|
_id: Default::default(),
|
||||||
|
callback,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_id(&self) -> String {
|
||||||
|
self._id.to_hex()
|
||||||
|
}
|
||||||
|
|
||||||
|
query_call_consume!(store, self, db, Self, {
|
||||||
|
let db = db.get_database().await;
|
||||||
|
let ci = db.collection::<Self>("callback_info");
|
||||||
|
|
||||||
|
ci.insert_one(&self).await?;
|
||||||
|
|
||||||
|
Ok(self)
|
||||||
|
});
|
||||||
|
|
||||||
|
pub async fn get<D: CallDB>(db: &mut D, id: &str) -> DbResult<Option<Self>> {
|
||||||
|
let db = db.get_database().await;
|
||||||
|
let ci = db.collection::<Self>("callback_info");
|
||||||
|
|
||||||
|
let id = match ObjectId::parse_str(id) {
|
||||||
|
Ok(id) => id,
|
||||||
|
Err(_) => return Ok(None),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(ci
|
||||||
|
.find_one(doc! {
|
||||||
|
"_id": id
|
||||||
|
})
|
||||||
|
.await?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_callback<D: CallDB>(db: &mut D, id: &str) -> DbResult<Option<C>> {
|
||||||
|
Self::get(db, id).await.map(|co| co.map(|c| c.callback))
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,3 +1,5 @@
|
|||||||
|
pub mod callback_info;
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use enum_stringify::EnumStringify;
|
use enum_stringify::EnumStringify;
|
||||||
@ -35,6 +37,7 @@ pub struct User {
|
|||||||
pub language_code: Option<String>,
|
pub language_code: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
macro_rules! query_call {
|
macro_rules! query_call {
|
||||||
($func_name:ident, $self:ident, $db:ident, $return_type:ty, $body:block) => {
|
($func_name:ident, $self:ident, $db:ident, $return_type:ty, $body:block) => {
|
||||||
pub async fn $func_name<D: CallDB>(&$self, $db: &mut D)
|
pub async fn $func_name<D: CallDB>(&$self, $db: &mut D)
|
||||||
@ -42,6 +45,14 @@ macro_rules! query_call {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! query_call_consume {
|
||||||
|
($func_name:ident, $self:ident, $db:ident, $return_type:ty, $body:block) => {
|
||||||
|
pub async fn $func_name<D: CallDB>($self, $db: &mut D)
|
||||||
|
-> DbResult<$return_type> $body
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
impl User {
|
impl User {
|
||||||
query_call!(update_user, self, db, (), {
|
query_call!(update_user, self, db, (), {
|
||||||
let db_collection = db.get_database().await.collection::<Self>("users");
|
let db_collection = db.get_database().await.collection::<Self>("users");
|
||||||
@ -136,7 +147,11 @@ impl CallDB for DB {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type DbError = mongodb::error::Error;
|
#[derive(thiserror::Error, Debug)]
|
||||||
|
pub enum DbError {
|
||||||
|
#[error("error while processing mongodb query: {0}")]
|
||||||
|
MongodbError(#[from] mongodb::error::Error),
|
||||||
|
}
|
||||||
pub type DbResult<T> = Result<T, DbError>;
|
pub type DbResult<T> = Result<T, DbError>;
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
@ -148,7 +163,7 @@ pub trait CallDB {
|
|||||||
let db = self.get_database().await;
|
let db = self.get_database().await;
|
||||||
let users = db.collection::<User>("users");
|
let users = db.collection::<User>("users");
|
||||||
|
|
||||||
users.find(doc! {}).await?.try_collect().await
|
Ok(users.find(doc! {}).await?.try_collect().await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn set_admin(&mut self, userid: i64, isadmin: bool) -> DbResult<()> {
|
async fn set_admin(&mut self, userid: i64, isadmin: bool) -> DbResult<()> {
|
||||||
@ -268,7 +283,7 @@ pub trait CallDB {
|
|||||||
let db = self.get_database().await;
|
let db = self.get_database().await;
|
||||||
let events = db.collection::<Event>("events");
|
let events = db.collection::<Event>("events");
|
||||||
|
|
||||||
events.find(doc! {}).await?.try_collect().await
|
Ok(events.find(doc! {}).await?.try_collect().await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn create_event(&mut self, event_datetime: chrono::DateTime<Utc>) -> DbResult<Event> {
|
async fn create_event(&mut self, event_datetime: chrono::DateTime<Utc>) -> DbResult<Event> {
|
||||||
|
|||||||
28
src/db/tests/callback_info_tests.rs
Normal file
28
src/db/tests/callback_info_tests.rs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
use super::super::callback_info::CallbackInfo;
|
||||||
|
use super::setup_db;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Default)]
|
||||||
|
#[serde(tag = "type")]
|
||||||
|
#[serde(rename = "snake_case")]
|
||||||
|
pub enum Callback {
|
||||||
|
#[default]
|
||||||
|
MoreInfo,
|
||||||
|
NextPage,
|
||||||
|
}
|
||||||
|
|
||||||
|
type CI = CallbackInfo<Callback>;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_store() {
|
||||||
|
let mut db = setup_db().await;
|
||||||
|
|
||||||
|
let ci = CI::new(Default::default());
|
||||||
|
|
||||||
|
let ci = ci.store(&mut db).await.unwrap();
|
||||||
|
|
||||||
|
let ci = CI::get(&mut db, &ci.get_id()).await.unwrap();
|
||||||
|
|
||||||
|
assert!(ci.is_some());
|
||||||
|
}
|
||||||
@ -1,5 +1,6 @@
|
|||||||
#![allow(clippy::unwrap_used)]
|
#![allow(clippy::unwrap_used)]
|
||||||
|
|
||||||
|
mod callback_info_tests;
|
||||||
use dotenvy;
|
use dotenvy;
|
||||||
|
|
||||||
use super::CallDB;
|
use super::CallDB;
|
||||||
80
src/main.rs
80
src/main.rs
@ -1,9 +1,12 @@
|
|||||||
pub mod admin;
|
pub mod admin;
|
||||||
pub mod db;
|
pub mod db;
|
||||||
pub mod mongodb_storage;
|
pub mod mongodb_storage;
|
||||||
|
pub mod utils;
|
||||||
|
|
||||||
use log::info;
|
use db::callback_info::CallbackInfo;
|
||||||
|
use log::{info, warn};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
use utils::create_callback_button;
|
||||||
|
|
||||||
use crate::admin::{admin_command_handler, AdminCommands};
|
use crate::admin::{admin_command_handler, AdminCommands};
|
||||||
use crate::admin::{secret_command_handler, SecretCommands};
|
use crate::admin::{secret_command_handler, SecretCommands};
|
||||||
@ -70,6 +73,16 @@ pub enum State {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
#[serde(tag = "type")]
|
||||||
|
#[serde(rename = "snake_case")]
|
||||||
|
pub enum Callback {
|
||||||
|
MoreInfo,
|
||||||
|
ProjectPage { id: u32 },
|
||||||
|
}
|
||||||
|
|
||||||
|
type CallbackStore = CallbackInfo<Callback>;
|
||||||
|
|
||||||
pub struct BotController {
|
pub struct BotController {
|
||||||
pub bot: Bot,
|
pub bot: Bot,
|
||||||
pub db: DB,
|
pub db: DB,
|
||||||
@ -175,21 +188,39 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
async fn callback_handler(bot: Bot, mut db: DB, q: CallbackQuery) -> BotResult<()> {
|
async fn callback_handler(bot: Bot, mut db: DB, q: CallbackQuery) -> BotResult<()> {
|
||||||
bot.answer_callback_query(&q.id).await?;
|
bot.answer_callback_query(&q.id).await?;
|
||||||
|
|
||||||
if let Some(ref data) = q.data {
|
let data = match q.data {
|
||||||
match data.as_str() {
|
Some(ref data) => data,
|
||||||
"more_info" => {
|
None => {
|
||||||
answer_message(
|
// not really our case to handle
|
||||||
&bot,
|
return Ok(());
|
||||||
q.chat_id().map(|i| i.0).unwrap_or(q.from.id.0 as i64),
|
|
||||||
&mut db,
|
|
||||||
"more_info",
|
|
||||||
None as Option<InlineKeyboardMarkup>,
|
|
||||||
)
|
|
||||||
.await?
|
|
||||||
}
|
|
||||||
_ => {} // do nothing, yet
|
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
|
let callback = match CallbackStore::get_callback(&mut db, data).await? {
|
||||||
|
Some(callback) => callback,
|
||||||
|
None => {
|
||||||
|
warn!("Not found callback for data: {data}");
|
||||||
|
// doing this silently beacuse end user shouldn't know about backend internal data
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
match callback {
|
||||||
|
Callback::MoreInfo => {
|
||||||
|
answer_message(
|
||||||
|
&bot,
|
||||||
|
q.chat_id().map(|i| i.0).unwrap_or(q.from.id.0 as i64),
|
||||||
|
&mut db,
|
||||||
|
"more_info",
|
||||||
|
None as Option<InlineKeyboardMarkup>,
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
}
|
||||||
|
Callback::ProjectPage { id } => {
|
||||||
|
bot.send_message(q.from.id, format!("Some project No: {id}"))
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -580,10 +611,21 @@ async fn make_start_buttons(db: &mut DB) -> BotResult<InlineKeyboardMarkup> {
|
|||||||
)]
|
)]
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
buttons.push(vec![InlineKeyboardButton::callback(
|
buttons.push(vec![
|
||||||
"More info",
|
InlineKeyboardButton::callback(
|
||||||
"more_info",
|
"More info",
|
||||||
)]);
|
CallbackStore::new(Callback::MoreInfo)
|
||||||
|
.store(db)
|
||||||
|
.await?
|
||||||
|
.get_id(),
|
||||||
|
),
|
||||||
|
create_callback_button(
|
||||||
|
"show_projects",
|
||||||
|
CallbackStore::new(Callback::ProjectPage { id: 1 }),
|
||||||
|
db,
|
||||||
|
)
|
||||||
|
.await?,
|
||||||
|
]);
|
||||||
|
|
||||||
Ok(InlineKeyboardMarkup::new(buttons))
|
Ok(InlineKeyboardMarkup::new(buttons))
|
||||||
}
|
}
|
||||||
|
|||||||
28
src/utils.rs
Normal file
28
src/utils.rs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use teloxide::types::InlineKeyboardButton;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
db::{callback_info::CallbackInfo, CallDB},
|
||||||
|
BotResult,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub async fn create_callback_button<C, D>(
|
||||||
|
literal: &str,
|
||||||
|
ci: CallbackInfo<C>,
|
||||||
|
db: &mut D,
|
||||||
|
) -> BotResult<InlineKeyboardButton>
|
||||||
|
where
|
||||||
|
C: Serialize + for<'a> Deserialize<'a> + Send + Sync,
|
||||||
|
D: CallDB + Send,
|
||||||
|
{
|
||||||
|
let text = db
|
||||||
|
.get_literal_value(literal)
|
||||||
|
.await?
|
||||||
|
.unwrap_or("Please, set content of this message".into());
|
||||||
|
let ci = ci.store(db).await?;
|
||||||
|
|
||||||
|
Ok(InlineKeyboardButton::new(
|
||||||
|
text,
|
||||||
|
teloxide::types::InlineKeyboardButtonKind::CallbackData(ci.get_id()),
|
||||||
|
))
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user