Compare commits

..

11 Commits

Author SHA1 Message Date
projectmoon 59fc281a0c Merge pull request 'Record room information (ID, name) in the database' (#48) from record-room-info into master
continuous-integration/drone/push Build was killed Details
Reviewed-on: projectmoon/matrix-dicebot#48
2020-11-30 20:16:00 +00:00
projectmoon e177da9c25 Centralize record_room_information function.
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is failing Details
2020-11-30 19:53:26 +00:00
projectmoon a65084e04a Unit test for updating room info data.
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/pr Build is passing Details
2020-11-29 21:33:46 +00:00
projectmoon 979dc8ea34 Clearer test name for clearing room info. 2020-11-29 21:29:35 +00:00
projectmoon 0b2246cbd5 Unit tests for inserting and loading room info from db. 2020-11-29 21:29:13 +00:00
projectmoon 7e1abab66d Resync command now uses record_room_information.
continuous-integration/drone/push Build is passing Details
2020-11-29 21:18:41 +00:00
projectmoon 569ba4f2e0 Carry full room instance in context instead of just room id.
continuous-integration/drone/push Build is passing Details
2020-11-29 21:03:45 +00:00
projectmoon 118e4b00c7 Store room info when joining a room. 2020-11-29 17:06:04 +00:00
projectmoon c8c38ac1d4 Avoid nested map when retrieving room info from db.
continuous-integration/drone/push Build is passing Details
2020-11-29 16:55:23 +00:00
projectmoon 91cfc52e5b Change record_users_in_room to record_room_information.
continuous-integration/drone/push Build is passing Details
2020-11-29 14:02:40 +00:00
projectmoon 224f8cd0f1 Functions for storing RoomInfo in db. Refactor bot joins room event.
continuous-integration/drone/push Build is passing Details
Add get/insert functions for RoomInfo in the rooms db.

Move 'bot joins room' code to single method, so we can also record a
RoomInfo struct into the database.
2020-11-29 14:00:05 +00:00
14 changed files with 182 additions and 37 deletions

View File

@ -2,10 +2,20 @@ use chronicle_dicebot::commands;
use chronicle_dicebot::context::Context; use chronicle_dicebot::context::Context;
use chronicle_dicebot::db::Database; use chronicle_dicebot::db::Database;
use chronicle_dicebot::error::BotError; use chronicle_dicebot::error::BotError;
use matrix_sdk::{
identifiers::{room_id, user_id},
Room,
};
fn dummy_room() -> Room {
Room::new(
&room_id!("!fakeroomid:example.com"),
&user_id!("@fakeuserid:example.com"),
)
}
#[tokio::main] #[tokio::main]
async fn main() -> Result<(), BotError> { async fn main() -> Result<(), BotError> {
let db = Database::new_temp()?;
let input = std::env::args().skip(1).collect::<Vec<String>>().join(" "); let input = std::env::args().skip(1).collect::<Vec<String>>().join(" ");
let command = match commands::parser::parse_command(&input) { let command = match commands::parser::parse_command(&input) {
Ok(command) => command, Ok(command) => command,
@ -13,10 +23,10 @@ async fn main() -> Result<(), BotError> {
}; };
let context = Context { let context = Context {
db: db, db: Database::new_temp()?,
matrix_client: &matrix_sdk::Client::new("http://example.com") matrix_client: &matrix_sdk::Client::new("http://example.com")
.expect("Could not create matrix client"), .expect("Could not create matrix client"),
room_id: "roomid", room: &dummy_room(),
username: "@localuser:example.com", username: "@localuser:example.com",
message_body: &input, message_body: &input,
}; };

View File

@ -130,7 +130,7 @@ impl DiceBot {
let ctx = Context { let ctx = Context {
db: self.db.clone(), db: self.db.clone(),
matrix_client: &self.client, matrix_client: &self.client,
room_id: room_id.as_str(), room: room,
username: &sender_username, username: &sender_username,
message_body: &command, message_body: &command,
}; };

View File

@ -1,7 +1,7 @@
use super::DiceBot; use super::DiceBot;
use crate::db::Database; use crate::db::Database;
use crate::error::BotError; use crate::error::BotError;
use crate::matrix; use crate::logic::record_room_information;
use async_trait::async_trait; use async_trait::async_trait;
use log::{debug, error, info, warn}; use log::{debug, error, info, warn};
use matrix_sdk::{ use matrix_sdk::{
@ -123,13 +123,8 @@ impl EventEmitter for DiceBot {
info!("Clearing all information for room ID {}", room_id); info!("Clearing all information for room ID {}", room_id);
self.db.rooms.clear_info(room_id) self.db.rooms.clear_info(room_id)
} else if event_affects_us && adding_user { } else if event_affects_us && adding_user {
info!("Joined room {}; recording user information", room_id); info!("Joined room {}; recording room information", room_id);
let usernames = matrix::get_users_in_room(&self.client, &room.room_id).await; record_room_information(&self.client, &self.db, &room, &event.state_key).await
usernames
.into_iter()
.filter(|username| username != &event.state_key)
.map(|username| self.db.rooms.add_user_to_room(&username, room_id))
.collect() //Make use of collect impl on Result.
} else if !event_affects_us && adding_user { } else if !event_affects_us && adding_user {
info!("Adding user {} to room ID {}", username, room_id); info!("Adding user {} to room ID {}", username, room_id);
self.db.rooms.add_user_to_room(username, room_id) self.db.rooms.add_user_to_room(username, room_id)

View File

@ -323,6 +323,14 @@ mod tests {
use super::*; use super::*;
use crate::db::Database; use crate::db::Database;
/// Create dummy room instance.
fn dummy_room() -> matrix_sdk::Room {
matrix_sdk::Room::new(
&matrix_sdk::identifiers::room_id!("!fakeroomid:example.com"),
&matrix_sdk::identifiers::user_id!("@fakeuserid:example.com"),
)
}
///Instead of being random, generate a series of numbers we have complete ///Instead of being random, generate a series of numbers we have complete
///control over. ///control over.
struct SequentialDieRoller { struct SequentialDieRoller {
@ -464,7 +472,7 @@ mod tests {
let ctx = Context { let ctx = Context {
db: db, db: db,
matrix_client: &matrix_sdk::Client::new("http://example.com").unwrap(), matrix_client: &matrix_sdk::Client::new("http://example.com").unwrap(),
room_id: "roomid", room: &dummy_room(),
username: "username", username: "username",
message_body: "message", message_body: "message",
}; };
@ -495,7 +503,7 @@ mod tests {
let ctx = Context { let ctx = Context {
db: db, db: db,
matrix_client: &matrix_sdk::Client::new("http://example.com").unwrap(), matrix_client: &matrix_sdk::Client::new("http://example.com").unwrap(),
room_id: "roomid", room: &dummy_room(),
username: "username", username: "username",
message_body: "message", message_body: "message",
}; };
@ -519,15 +527,18 @@ mod tests {
#[tokio::test] #[tokio::test]
async fn can_resolve_variables_test() { async fn can_resolve_variables_test() {
use crate::db::variables::UserAndRoom;
let db = Database::new_temp().unwrap(); let db = Database::new_temp().unwrap();
let ctx = Context { let ctx = Context {
db: db.clone(), db: db.clone(),
matrix_client: &matrix_sdk::Client::new("http://example.com").unwrap(), matrix_client: &matrix_sdk::Client::new("http://example.com").unwrap(),
room_id: "roomid", room: &dummy_room(),
username: "username", username: "username",
message_body: "message", message_body: "message",
}; };
let user_and_room = crate::db::variables::UserAndRoom(&ctx.username, &ctx.room_id);
let user_and_room = UserAndRoom(&ctx.username, &ctx.room.room_id.as_str());
db.variables db.variables
.set_user_variable(&user_and_room, "myvariable", 10) .set_user_variable(&user_and_room, "myvariable", 10)

View File

@ -78,13 +78,21 @@ pub async fn execute_command(ctx: &Context<'_>) -> CommandResult {
mod tests { mod tests {
use super::*; use super::*;
/// Create a dummy room instance.
fn dummy_room() -> matrix_sdk::Room {
matrix_sdk::Room::new(
&matrix_sdk::identifiers::room_id!("!fakeroomid:example.com"),
&matrix_sdk::identifiers::user_id!("@fakeuserid:example.com"),
)
}
#[tokio::test] #[tokio::test]
async fn unrecognized_command() { async fn unrecognized_command() {
let db = crate::db::Database::new_temp().unwrap(); let db = crate::db::Database::new_temp().unwrap();
let ctx = Context { let ctx = Context {
db: db, db: db,
matrix_client: &matrix_sdk::Client::new("http://example.com").unwrap(), matrix_client: &matrix_sdk::Client::new("http://example.com").unwrap(),
room_id: "myroomid", room: &dummy_room(),
username: "myusername", username: "myusername",
message_body: "!notacommand", message_body: "!notacommand",
}; };

View File

@ -1,10 +1,9 @@
use super::{Command, Execution}; use super::{Command, Execution};
use crate::context::Context; use crate::context::Context;
use crate::db::errors::DataError; use crate::db::errors::DataError;
use crate::matrix; use crate::logic::record_room_information;
use async_trait::async_trait; use async_trait::async_trait;
use matrix_sdk::identifiers::{RoomId, UserId}; use matrix_sdk::identifiers::UserId;
use std::convert::TryFrom;
pub struct ResyncCommand; pub struct ResyncCommand;
@ -17,17 +16,11 @@ impl Command for ResyncCommand {
} }
async fn execute(&self, ctx: &Context<'_>) -> Execution { async fn execute(&self, ctx: &Context<'_>) -> Execution {
let room_id = RoomId::try_from(ctx.room_id).expect("failed to decode room ID");
let our_username: Option<UserId> = ctx.matrix_client.user_id().await; let our_username: Option<UserId> = ctx.matrix_client.user_id().await;
let our_username: &str = our_username.as_ref().map_or("", UserId::as_str); let our_username: &str = our_username.as_ref().map_or("", UserId::as_str);
let usernames = matrix::get_users_in_room(&ctx.matrix_client, &room_id).await; let result: ResyncResult =
record_room_information(ctx.matrix_client, &ctx.db, &ctx.room, our_username).await;
let result: ResyncResult = usernames
.into_iter()
.filter(|username| username != our_username)
.map(|username| ctx.db.rooms.add_user_to_room(&username, room_id.as_str()))
.collect(); //Make use of collect impl on Result.
let (plain, html) = match result { let (plain, html) = match result {
Ok(()) => { Ok(()) => {

View File

@ -13,7 +13,7 @@ impl Command for GetAllVariablesCommand {
} }
async fn execute(&self, ctx: &Context<'_>) -> Execution { async fn execute(&self, ctx: &Context<'_>) -> Execution {
let key = UserAndRoom(&ctx.username, &ctx.room_id); let key = UserAndRoom(&ctx.username, &ctx.room.room_id.as_str());
let result = ctx.db.variables.get_user_variables(&key); let result = ctx.db.variables.get_user_variables(&key);
let value = match result { let value = match result {
@ -48,7 +48,7 @@ impl Command for GetVariableCommand {
async fn execute(&self, ctx: &Context<'_>) -> Execution { async fn execute(&self, ctx: &Context<'_>) -> Execution {
let name = &self.0; let name = &self.0;
let key = UserAndRoom(&ctx.username, &ctx.room_id); let key = UserAndRoom(&ctx.username, &ctx.room.room_id.as_str());
let result = ctx.db.variables.get_user_variable(&key, name); let result = ctx.db.variables.get_user_variable(&key, name);
let value = match result { let value = match result {
@ -74,7 +74,7 @@ impl Command for SetVariableCommand {
async fn execute(&self, ctx: &Context<'_>) -> Execution { async fn execute(&self, ctx: &Context<'_>) -> Execution {
let name = &self.0; let name = &self.0;
let value = self.1; let value = self.1;
let key = UserAndRoom(&ctx.username, ctx.room_id); let key = UserAndRoom(&ctx.username, ctx.room.room_id.as_str());
let result = ctx.db.variables.set_user_variable(&key, name, value); let result = ctx.db.variables.set_user_variable(&key, name, value);
let content = match result { let content = match result {
@ -98,7 +98,7 @@ impl Command for DeleteVariableCommand {
async fn execute(&self, ctx: &Context<'_>) -> Execution { async fn execute(&self, ctx: &Context<'_>) -> Execution {
let name = &self.0; let name = &self.0;
let key = UserAndRoom(&ctx.username, ctx.room_id); let key = UserAndRoom(&ctx.username, ctx.room.room_id.as_str());
let result = ctx.db.variables.delete_user_variable(&key, name); let result = ctx.db.variables.delete_user_variable(&key, name);
let value = match result { let value = match result {

View File

@ -1,5 +1,6 @@
use crate::db::Database; use crate::db::Database;
use matrix_sdk::Client; use matrix_sdk::Client;
use matrix_sdk::Room;
/// A context carried through the system providing access to things /// A context carried through the system providing access to things
/// like the database. /// like the database.
@ -7,7 +8,7 @@ use matrix_sdk::Client;
pub struct Context<'a> { pub struct Context<'a> {
pub db: Database, pub db: Database,
pub matrix_client: &'a Client, pub matrix_client: &'a Client,
pub room_id: &'a str, pub room: &'a Room,
pub username: &'a str, pub username: &'a str,
pub message_body: &'a str, pub message_body: &'a str,
} }

View File

@ -343,6 +343,14 @@ mod tests {
use crate::db::Database; use crate::db::Database;
use crate::parser::{Amount, Element, Operator}; use crate::parser::{Amount, Element, Operator};
/// Create a dummy room instance.
fn dummy_room() -> matrix_sdk::Room {
matrix_sdk::Room::new(
&matrix_sdk::identifiers::room_id!("!fakeroomid:example.com"),
&matrix_sdk::identifiers::user_id!("@fakeuserid:example.com"),
)
}
/// Generate a series of numbers manually for testing. For this /// Generate a series of numbers manually for testing. For this
/// die system, the first roll in the Vec should be the unit roll, /// die system, the first roll in the Vec should be the unit roll,
/// and any subsequent rolls should be the tens place roll. The /// and any subsequent rolls should be the tens place roll. The
@ -383,7 +391,7 @@ mod tests {
let ctx = Context { let ctx = Context {
db: db, db: db,
matrix_client: &matrix_sdk::Client::new("https://example.com").unwrap(), matrix_client: &matrix_sdk::Client::new("https://example.com").unwrap(),
room_id: "roomid", room: &dummy_room(),
username: "username", username: "username",
message_body: "message", message_body: "message",
}; };

View File

@ -1,5 +1,6 @@
use crate::db::errors::DataError; use crate::db::errors::DataError;
use crate::db::schema::convert_u64; use crate::db::schema::convert_u64;
use crate::models::RoomInfo;
use byteorder::BigEndian; use byteorder::BigEndian;
use log::{debug, error, log_enabled}; use log::{debug, error, log_enabled};
use sled::transaction::TransactionalTree; use sled::transaction::TransactionalTree;
@ -14,7 +15,8 @@ use zerocopy::AsBytes;
#[derive(Clone)] #[derive(Clone)]
pub struct Rooms { pub struct Rooms {
/// Room ID -> RoomInfo struct (single entries) /// Room ID -> RoomInfo struct (single entries).
/// Key is just room ID as bytes.
pub(in crate::db) roomid_roominfo: Tree, pub(in crate::db) roomid_roominfo: Tree,
/// Room ID -> list of usernames in room. /// Room ID -> list of usernames in room.
@ -225,6 +227,25 @@ impl Rooms {
} }
} }
pub fn insert_room_info(&self, info: &RoomInfo) -> Result<(), DataError> {
let key = info.room_id.as_bytes();
let serialized = bincode::serialize(&info)?;
self.roomid_roominfo.insert(key, serialized)?;
Ok(())
}
pub fn get_room_info(&self, room_id: &str) -> Result<Option<RoomInfo>, DataError> {
let key = room_id.as_bytes();
let room_info: Option<RoomInfo> = self
.roomid_roominfo
.get(key)?
.map(|bytes| bincode::deserialize(&bytes))
.transpose()?;
Ok(room_info)
}
pub fn get_rooms_for_user(&self, username: &str) -> Result<HashSet<String>, DataError> { pub fn get_rooms_for_user(&self, username: &str) -> Result<HashSet<String>, DataError> {
hashset_tree::get_set(&self.username_roomids, username.as_bytes()) hashset_tree::get_set(&self.username_roomids, username.as_bytes())
} }
@ -357,7 +378,71 @@ mod tests {
} }
#[test] #[test]
fn clear_info() { fn insert_room_info_works() {
let rooms = create_test_instance();
let info = RoomInfo {
room_id: matrix_sdk::identifiers::room_id!("!fakeroom:example.com")
.as_str()
.to_owned(),
room_name: "fake room name".to_owned(),
};
rooms
.insert_room_info(&info)
.expect("Could insert room info");
let found_info = rooms
.get_room_info("!fakeroom:example.com")
.expect("Error loading room info");
assert!(found_info.is_some());
assert_eq!(info, found_info.unwrap());
}
#[test]
fn insert_room_info_updates_data() {
let rooms = create_test_instance();
let mut info = RoomInfo {
room_id: matrix_sdk::identifiers::room_id!("!fakeroom:example.com")
.as_str()
.to_owned(),
room_name: "fake room name".to_owned(),
};
rooms
.insert_room_info(&info)
.expect("Could insert room info");
//Update info to have a new room name before inserting again.
info.room_name = "new fake room name".to_owned();
rooms
.insert_room_info(&info)
.expect("Could insert room info");
let found_info = rooms
.get_room_info("!fakeroom:example.com")
.expect("Error loading room info");
assert!(found_info.is_some());
assert_eq!(info, found_info.unwrap());
}
#[test]
fn get_room_info_none_when_room_does_not_exist() {
let rooms = create_test_instance();
let found_info = rooms
.get_room_info("!fakeroom:example.com")
.expect("Error loading room info");
assert!(found_info.is_none());
}
#[test]
fn clear_info_modifies_removes_requested_room() {
let rooms = create_test_instance(); let rooms = create_test_instance();
rooms rooms

View File

@ -9,7 +9,7 @@ use futures::stream::{self, StreamExt, TryStreamExt};
//New hotness //New hotness
pub async fn calculate_dice_amount(amounts: &[Amount], ctx: &Context<'_>) -> Result<i32, BotError> { pub async fn calculate_dice_amount(amounts: &[Amount], ctx: &Context<'_>) -> Result<i32, BotError> {
let stream = stream::iter(amounts); let stream = stream::iter(amounts);
let key = UserAndRoom(&ctx.username, &ctx.room_id); let key = UserAndRoom(&ctx.username, ctx.room.room_id.as_str());
let variables = &ctx.db.variables.get_user_variables(&key)?; let variables = &ctx.db.variables.get_user_variables(&key)?;
use DiceRollingError::VariableNotFound; use DiceRollingError::VariableNotFound;

View File

@ -9,6 +9,7 @@ pub mod db;
pub mod dice; pub mod dice;
pub mod error; pub mod error;
mod help; mod help;
pub mod logic;
pub mod matrix; pub mod matrix;
pub mod models; pub mod models;
mod parser; mod parser;

30
src/logic.rs Normal file
View File

@ -0,0 +1,30 @@
use crate::db::errors::DataError;
use crate::matrix;
use crate::models::RoomInfo;
use matrix_sdk::{self, Client, Room};
/// Record the information about a room, including users in it.
pub async fn record_room_information(
client: &Client,
db: &crate::db::Database,
room: &Room,
our_username: &str,
) -> Result<(), DataError> {
let room_id_str = room.room_id.as_str();
let usernames = matrix::get_users_in_room(&client, &room.room_id).await;
let info = RoomInfo {
room_id: room_id_str.to_owned(),
room_name: room.display_name(),
};
// TODO this and the username adding should be one whole
// transaction in the db.
db.rooms.insert_room_info(&info)?;
usernames
.into_iter()
.filter(|username| username != our_username)
.map(|username| db.rooms.add_user_to_room(&username, room_id_str))
.collect() //Make use of collect impl on Result.
}

View File

@ -1,4 +1,7 @@
use serde::{Deserialize, Serialize};
/// RoomInfo has basic metadata about a room: its name, ID, etc. /// RoomInfo has basic metadata about a room: its name, ID, etc.
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub struct RoomInfo { pub struct RoomInfo {
pub room_id: String, pub room_id: String,
pub room_name: String, pub room_name: String,