Convert to SQLx and SQLite #64
|
@ -1,5 +1,47 @@
|
|||
{
|
||||
"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": {
|
||||
"query": "SELECT value as \"value: i32\" FROM user_variables\n WHERE user_id = ? AND room_id = ? AND key = ?",
|
||||
"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": {
|
||||
"query": "SELECT key, value as \"value: i32\" FROM user_variables\n WHERE room_id = ?",
|
||||
"describe": {
|
||||
|
|
|
@ -161,12 +161,6 @@ 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");
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
use std::num::TryFromIntError;
|
||||
|
||||
use sled::transaction::{TransactionError, UnabortableTransactionError};
|
||||
use thiserror::Error;
|
||||
|
||||
|
@ -52,6 +54,9 @@ pub enum DataError {
|
|||
|
||||
#[error("sqlx error: {0}")]
|
||||
SqlxError(#[from] sqlx::Error),
|
||||
|
||||
#[error("numeric conversion error")]
|
||||
NumericConversionError(#[from] TryFromIntError),
|
||||
}
|
||||
|
||||
/// This From implementation is necessary to deal with the recursive
|
||||
|
|
|
@ -2,42 +2,141 @@ use super::errors::DataError;
|
|||
use super::{Database, Rooms};
|
||||
use crate::models::RoomInfo;
|
||||
use async_trait::async_trait;
|
||||
use sqlx::SqlitePool;
|
||||
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]
|
||||
impl Rooms for Database {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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(())
|
||||
}
|
||||
|
||||
async fn get_room_info(&self, room_id: &str) -> Result<Option<RoomInfo>, DataError> {
|
||||
Ok(Some(RoomInfo {
|
||||
room_id: "".to_string(),
|
||||
room_name: "".to_string(),
|
||||
let info = sqlx::query!(
|
||||
r#"SELECT room_id, room_name FROM room_info
|
||||
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> {
|
||||
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> {
|
||||
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> {
|
||||
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(())
|
||||
}
|
||||
|
||||
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(())
|
||||
}
|
||||
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,10 +5,23 @@ use async_trait::async_trait;
|
|||
#[async_trait]
|
||||
impl DbState for Database {
|
||||
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> {
|
||||
sqlx::query(
|
||||
r#"INSERT INTO bot_state
|
||||
(device_id)
|
||||
VALUES (?)"#,
|
||||
)
|
||||
.bind(device_id)
|
||||
.execute(&self.conn)
|
||||
.await?;
|
||||
|
||||
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