Add proper constraints to db tables. Report errors when listing users.
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/pr Build is passing Details

This commit is contained in:
projectmoon 2021-05-16 21:39:19 +00:00
parent 9798821b7b
commit bfc5609ab6
8 changed files with 97 additions and 33 deletions

View File

@ -161,19 +161,25 @@ impl DiceBot {
let client = self.client.clone();
self.login(&client).await?;
// Initial sync without event handler prevents responding to
// messages received while bot was offline. TODO: selectively
// respond to old messages? e.g. comands missed while offline.
//info!("Performing intial sync (no commands will be responded to)");
// self.client.sync_once(SyncSettings::default()).await?;
client.set_event_handler(Box::new(self)).await;
info!("Listening for commands");
let token = client
.sync_token()
.await
.ok_or(BotError::SyncTokenRequired)?;
// let token = client
// .sync_token()
// .await
// .ok_or(BotError::SyncTokenRequired)?;
let settings = SyncSettings::default().token(token);
// let settings = SyncSettings::default().token(token);
// TODO replace with sync_with_callback for cleaner shutdown
// process.
client.sync(settings).await;
client.sync(SyncSettings::default()).await;
Ok(())
}

View File

@ -136,7 +136,7 @@ impl EventHandler for DiceBot {
let result = if event_affects_us && !adding_user {
info!("Clearing all information for room ID {}", room_id);
self.db.clear_info(room_id_str).await
self.db.clear_info(room_id_str).await.map_err(|e| e.into())
} else if event_affects_us && adding_user {
info!("Joined room {}; recording room information", room_id);
record_room_information(
@ -149,10 +149,16 @@ impl EventHandler for DiceBot {
.await
} else if !event_affects_us && adding_user {
info!("Adding user {} to room ID {}", username, room_id);
self.db.add_user_to_room(username, room_id_str).await
self.db
.add_user_to_room(username, room_id_str)
.await
.map_err(|e| e.into())
} else if !event_affects_us && !adding_user {
info!("Removing user {} from room ID {}", username, room_id);
self.db.remove_user_from_room(username, room_id_str).await
self.db
.remove_user_from_room(username, room_id_str)
.await
.map_err(|e| e.into())
} else {
debug!("Ignoring a room member event: {:#?}", event);
Ok(())

View File

@ -36,10 +36,10 @@ pub enum BotError {
#[error("error in matrix state store: {0}")]
MatrixStateStoreError(#[from] matrix_sdk::StoreError),
#[error("uncategorized matrix SDK error")]
#[error("uncategorized matrix SDK error: {0}")]
MatrixError(#[from] matrix_sdk::Error),
#[error("uncategorized matrix SDK base error")]
#[error("uncategorized matrix SDK base error: {0}")]
MatrixBaseError(#[from] matrix_sdk::BaseError),
#[error("future canceled")]

View File

@ -1,5 +1,6 @@
use crate::db::sqlite::errors::DataError;
use crate::db::sqlite::Rooms;
use crate::error::BotError;
use crate::matrix;
use crate::models::RoomInfo;
use futures::stream::{self, StreamExt, TryStreamExt};
@ -12,9 +13,12 @@ pub async fn record_room_information(
room_id: &RoomId,
room_display_name: &str,
our_username: &str,
) -> Result<(), DataError> {
) -> Result<(), BotError> {
//Clear out any old room info first.
db.clear_info(room_id.as_str()).await?;
let room_id_str = room_id.as_str();
let usernames = matrix::get_users_in_room(&client, &room_id).await;
let usernames = matrix::get_users_in_room(&client, &room_id).await?;
let info = RoomInfo {
room_id: room_id_str.to_owned(),
@ -29,12 +33,18 @@ pub async fn record_room_information(
.into_iter()
.filter(|username| username != our_username);
println!("Users to add to room: {:?}", filtered_usernames);
// Async collect into vec of results, then use into_iter of result
// to go to from Result<Vec<()>> to just Result<()>. Easier than
// attempting to async-collect our way to a single Result<()>.
stream::iter(filtered_usernames)
.then(|username| async move { db.add_user_to_room(&username, &room_id_str).await })
.collect::<Vec<Result<(), DataError>>>()
.then(|username| async move {
db.add_user_to_room(&username, &room_id_str)
.await
.map_err(|e| e.into())
})
.collect::<Vec<Result<(), BotError>>>()
.await
.into_iter()
.collect()

View File

@ -20,16 +20,19 @@ fn extract_error_message(error: MatrixError) -> String {
}
/// Retrieve a list of users in a given room.
pub async fn get_users_in_room(client: &Client, room_id: &RoomId) -> Vec<String> {
pub async fn get_users_in_room(
client: &Client,
room_id: &RoomId,
) -> Result<Vec<String>, MatrixError> {
if let Some(joined_room) = client.get_joined_room(room_id) {
let members = joined_room.joined_members().await.ok().unwrap_or_default();
let members = joined_room.joined_members().await?;
members
Ok(members
.into_iter()
.map(|member| member.user_id().to_string())
.collect()
.collect())
} else {
vec![]
Ok(vec![])
}
}

View File

@ -7,7 +7,6 @@ pub fn migration() -> String {
info!("Applying migration: {}", file!());
m.create_table("user_variables", |t| {
t.add_column("id", types::primary());
t.add_column("room_id", types::text());
t.add_column("user_id", types::text());
t.add_column("key", types::text());

View File

@ -1,31 +1,33 @@
use barrel::backend::Sqlite;
use barrel::{types, Migration};
use barrel::{types, types::Type, Migration};
use log::info;
fn primary_uuid() -> Type {
types::text().unique(true).primary(true).nullable(false)
}
fn autoincrement_int() -> Type {
types::integer()
.increments(true)
.unique(true)
.primary(false)
}
pub fn migration() -> String {
let mut m = Migration::new();
info!("Applying migration: {}", file!());
//Table for basic room information: room ID, room name
m.create_table("room_info", move |t| {
t.add_column("id", types::primary());
t.add_column("room_id", types::text());
t.add_column("room_id", primary_uuid());
t.add_column("room_name", types::text());
});
//Table of users in rooms.
m.create_table("room_users", move |t| {
t.add_column("id", types::primary());
t.add_column("room_id", types::text());
t.add_column("room_id", autoincrement_int());
t.add_column("username", types::text());
});
//Table of room ID, event ID, event timestamp
m.create_table("room_events", move |t| {
t.add_column("id", types::primary());
t.add_column("room_id", types::text());
t.add_column("event_id", types::text());
t.add_column("event_timestamp", types::integer());
});
m.make::<Sqlite>()
}

View File

@ -0,0 +1,38 @@
use barrel::backend::Sqlite;
use barrel::{types, types::Type, Migration};
use log::info;
fn primary_uuid() -> Type {
types::text().unique(true).nullable(false)
}
fn autoincrement_int() -> Type {
types::integer()
.increments(true)
.unique(true)
.primary(false)
}
pub fn migration() -> String {
let mut m = Migration::new();
info!("Applying migration: {}", file!());
//Table of room ID, event ID, event timestamp
m.create_table("room_events", move |t| {
t.add_column("room_id", types::text().nullable(false));
t.add_column("event_id", types::text().nullable(false));
t.add_column("event_timestamp", types::integer());
});
let mut res = m.make::<Sqlite>();
//This is a hack that gives us a composite primary key.
if res.ends_with(");") {
res.pop();
res.pop();
}
let x = format!("{}, PRIMARY KEY (room_id, event_id));", res);
println!("{}", x);
x
}