Implement room and dbstate for sqlite.
This commit is contained in:
parent
cf9ce63892
commit
9798821b7b
|
@ -1,5 +1,47 @@
|
||||||
{
|
{
|
||||||
"db": "SQLite",
|
"db": "SQLite",
|
||||||
|
"0ccdb5669e5819628096b8d2f81fef361f73dbcbc27428208a1a4e315a8c7880": {
|
||||||
|
"query": "SELECT room_id, room_name FROM room_info\n WHERE room_id = ?",
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"name": "room_id",
|
||||||
|
"ordinal": 0,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "room_name",
|
||||||
|
"ordinal": 1,
|
||||||
|
"type_info": "Text"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 1
|
||||||
|
},
|
||||||
|
"nullable": [
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"19d89370cac05c1bc4de0eb3508712da9ca133b1cf9445b5407d238f89c3ab0c": {
|
||||||
|
"query": "SELECT device_id FROM bot_state limit 1",
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"name": "device_id",
|
||||||
|
"ordinal": 0,
|
||||||
|
"type_info": "Text"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 0
|
||||||
|
},
|
||||||
|
"nullable": [
|
||||||
|
false
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
"636b1b868eaf04cd234fbf17747d94a66e81f7bc1b060ba14151dbfaf40eeefc": {
|
"636b1b868eaf04cd234fbf17747d94a66e81f7bc1b060ba14151dbfaf40eeefc": {
|
||||||
"query": "SELECT value as \"value: i32\" FROM user_variables\n WHERE user_id = ? AND room_id = ? AND key = ?",
|
"query": "SELECT value as \"value: i32\" FROM user_variables\n WHERE user_id = ? AND room_id = ? AND key = ?",
|
||||||
"describe": {
|
"describe": {
|
||||||
|
@ -18,6 +60,60 @@
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"ad52bc29fc1eef2bbff8b5f7316047f81ea31b1f910d0e0226125fea89530aa2": {
|
||||||
|
"query": "SELECT room_id FROM room_users\n WHERE username = ?",
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"name": "room_id",
|
||||||
|
"ordinal": 0,
|
||||||
|
"type_info": "Text"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 1
|
||||||
|
},
|
||||||
|
"nullable": [
|
||||||
|
false
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"b302d586e5ac4c72c2970361ea5a5936c0b8c6dad10033c626a0ce0404cadb25": {
|
||||||
|
"query": "SELECT username FROM room_users\n WHERE room_id = ?",
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"name": "username",
|
||||||
|
"ordinal": 0,
|
||||||
|
"type_info": "Text"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 1
|
||||||
|
},
|
||||||
|
"nullable": [
|
||||||
|
false
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"bba0fc255e7c30d1d2d9468c68ba38db6e8a13be035aa1152933ba9247b14f8c": {
|
||||||
|
"query": "SELECT event_id FROM room_events\n WHERE room_id = ? AND event_id = ?",
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"name": "event_id",
|
||||||
|
"ordinal": 0,
|
||||||
|
"type_info": "Text"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 2
|
||||||
|
},
|
||||||
|
"nullable": [
|
||||||
|
false
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
"d6558668b7395b95ded8da71c80963ddde957abdcc3c68b03431f8e904e0d21f": {
|
"d6558668b7395b95ded8da71c80963ddde957abdcc3c68b03431f8e904e0d21f": {
|
||||||
"query": "SELECT key, value as \"value: i32\" FROM user_variables\n WHERE room_id = ?",
|
"query": "SELECT key, value as \"value: i32\" FROM user_variables\n WHERE room_id = ?",
|
||||||
"describe": {
|
"describe": {
|
||||||
|
|
|
@ -161,12 +161,6 @@ impl DiceBot {
|
||||||
let client = self.client.clone();
|
let client = self.client.clone();
|
||||||
self.login(&client).await?;
|
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;
|
client.set_event_handler(Box::new(self)).await;
|
||||||
info!("Listening for commands");
|
info!("Listening for commands");
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
use std::num::TryFromIntError;
|
||||||
|
|
||||||
use sled::transaction::{TransactionError, UnabortableTransactionError};
|
use sled::transaction::{TransactionError, UnabortableTransactionError};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
@ -52,6 +54,9 @@ pub enum DataError {
|
||||||
|
|
||||||
#[error("sqlx error: {0}")]
|
#[error("sqlx error: {0}")]
|
||||||
SqlxError(#[from] sqlx::Error),
|
SqlxError(#[from] sqlx::Error),
|
||||||
|
|
||||||
|
#[error("numeric conversion error")]
|
||||||
|
NumericConversionError(#[from] TryFromIntError),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This From implementation is necessary to deal with the recursive
|
/// This From implementation is necessary to deal with the recursive
|
||||||
|
|
|
@ -2,42 +2,141 @@ use super::errors::DataError;
|
||||||
use super::{Database, Rooms};
|
use super::{Database, Rooms};
|
||||||
use crate::models::RoomInfo;
|
use crate::models::RoomInfo;
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
use sqlx::SqlitePool;
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
|
async fn record_event(conn: &SqlitePool, event_id: &str, room_id: &str) -> Result<(), DataError> {
|
||||||
|
use std::convert::TryFrom;
|
||||||
|
let now: i64 = i64::try_from(
|
||||||
|
SystemTime::now()
|
||||||
|
.duration_since(UNIX_EPOCH)
|
||||||
|
.expect("Clock has gone backwards")
|
||||||
|
.as_secs(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
sqlx::query(
|
||||||
|
r#"INSERT INTO room_events
|
||||||
|
(room_id, event_id, event_timestamp)
|
||||||
|
VALUES (?, ?, ?)"#,
|
||||||
|
)
|
||||||
|
.bind(room_id)
|
||||||
|
.bind(event_id)
|
||||||
|
.bind(now)
|
||||||
|
.execute(conn)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl Rooms for Database {
|
impl Rooms for Database {
|
||||||
async fn should_process(&self, room_id: &str, event_id: &str) -> Result<bool, DataError> {
|
async fn should_process(&self, room_id: &str, event_id: &str) -> Result<bool, DataError> {
|
||||||
|
let row = sqlx::query!(
|
||||||
|
r#"SELECT event_id FROM room_events
|
||||||
|
WHERE room_id = ? AND event_id = ?"#,
|
||||||
|
room_id,
|
||||||
|
event_id
|
||||||
|
)
|
||||||
|
.fetch_optional(&self.conn)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
match row {
|
||||||
|
Some(_) => Ok(false),
|
||||||
|
None => {
|
||||||
|
record_event(&self.conn, room_id, event_id).await?;
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async fn insert_room_info(&self, info: &RoomInfo) -> Result<(), DataError> {
|
async fn insert_room_info(&self, info: &RoomInfo) -> Result<(), DataError> {
|
||||||
|
sqlx::query(r#"INSERT INTO room_info (room_id, room_name) VALUES (?, ?)"#)
|
||||||
|
.bind(&info.room_id)
|
||||||
|
.bind(&info.room_name)
|
||||||
|
.execute(&self.conn)
|
||||||
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_room_info(&self, room_id: &str) -> Result<Option<RoomInfo>, DataError> {
|
async fn get_room_info(&self, room_id: &str) -> Result<Option<RoomInfo>, DataError> {
|
||||||
Ok(Some(RoomInfo {
|
let info = sqlx::query!(
|
||||||
room_id: "".to_string(),
|
r#"SELECT room_id, room_name FROM room_info
|
||||||
room_name: "".to_string(),
|
WHERE room_id = ?"#,
|
||||||
|
room_id
|
||||||
|
)
|
||||||
|
.fetch_optional(&self.conn)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(info.map(|i| RoomInfo {
|
||||||
|
room_id: i.room_id,
|
||||||
|
room_name: i.room_name,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_rooms_for_user(&self, user_id: &str) -> Result<HashSet<String>, DataError> {
|
async fn get_rooms_for_user(&self, user_id: &str) -> Result<HashSet<String>, DataError> {
|
||||||
Ok(HashSet::new())
|
let room_ids = sqlx::query!(
|
||||||
|
r#"SELECT room_id FROM room_users
|
||||||
|
WHERE username = ?"#,
|
||||||
|
user_id
|
||||||
|
)
|
||||||
|
.fetch_all(&self.conn)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(room_ids.into_iter().map(|row| row.room_id).collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_users_in_room(&self, room_id: &str) -> Result<HashSet<String>, DataError> {
|
async fn get_users_in_room(&self, room_id: &str) -> Result<HashSet<String>, DataError> {
|
||||||
Ok(HashSet::new())
|
let usernames = sqlx::query!(
|
||||||
|
r#"SELECT username FROM room_users
|
||||||
|
WHERE room_id = ?"#,
|
||||||
|
room_id
|
||||||
|
)
|
||||||
|
.fetch_all(&self.conn)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(usernames.into_iter().map(|row| row.username).collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn add_user_to_room(&self, username: &str, room_id: &str) -> Result<(), DataError> {
|
async fn add_user_to_room(&self, username: &str, room_id: &str) -> Result<(), DataError> {
|
||||||
|
self.remove_user_from_room(username, room_id).await.ok();
|
||||||
|
|
||||||
|
sqlx::query("INSERT INTO room_users (room_id, username) VALUES (?, ?)")
|
||||||
|
.bind(room_id)
|
||||||
|
.bind(username)
|
||||||
|
.execute(&self.conn)
|
||||||
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn remove_user_from_room(&self, username: &str, room_id: &str) -> Result<(), DataError> {
|
async fn remove_user_from_room(&self, username: &str, room_id: &str) -> Result<(), DataError> {
|
||||||
|
sqlx::query("DELETE FROM room_users where username = ? AND room_id = ?")
|
||||||
|
.bind(username)
|
||||||
|
.bind(room_id)
|
||||||
|
.execute(&self.conn)
|
||||||
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn clear_info(&self, room_id: &str) -> Result<(), DataError> {
|
async fn clear_info(&self, room_id: &str) -> Result<(), DataError> {
|
||||||
|
sqlx::query("DELETE FROM room_info where room_id = ?")
|
||||||
|
.bind(room_id)
|
||||||
|
.execute(&self.conn)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
sqlx::query("DELETE FROM room_users where room_id = ?")
|
||||||
|
.bind(room_id)
|
||||||
|
.execute(&self.conn)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
sqlx::query("DELETE FROM room_events where room_id = ?")
|
||||||
|
.bind(room_id)
|
||||||
|
.execute(&self.conn)
|
||||||
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,10 +5,23 @@ use async_trait::async_trait;
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl DbState for Database {
|
impl DbState for Database {
|
||||||
async fn get_device_id(&self) -> Result<Option<String>, DataError> {
|
async fn get_device_id(&self) -> Result<Option<String>, DataError> {
|
||||||
Ok(None)
|
let state = sqlx::query!(r#"SELECT device_id FROM bot_state limit 1"#)
|
||||||
|
.fetch_optional(&self.conn)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(state.map(|s| s.device_id))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn set_device_id(&self, device_id: &str) -> Result<(), DataError> {
|
async fn set_device_id(&self, device_id: &str) -> Result<(), DataError> {
|
||||||
|
sqlx::query(
|
||||||
|
r#"INSERT INTO bot_state
|
||||||
|
(device_id)
|
||||||
|
VALUES (?)"#,
|
||||||
|
)
|
||||||
|
.bind(device_id)
|
||||||
|
.execute(&self.conn)
|
||||||
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
use barrel::backend::Sqlite;
|
||||||
|
use barrel::{types, Migration};
|
||||||
|
use log::info;
|
||||||
|
|
||||||
|
pub fn migration() -> String {
|
||||||
|
let mut m = Migration::new();
|
||||||
|
info!("Applying migration: {}", file!());
|
||||||
|
|
||||||
|
//Basic state table with only device ID for now. Uses only one row.
|
||||||
|
m.create_table("bot_state", move |t| {
|
||||||
|
t.add_column("device_id", types::text());
|
||||||
|
});
|
||||||
|
|
||||||
|
m.make::<Sqlite>()
|
||||||
|
}
|
Loading…
Reference in New Issue