Remove diesel and use sea orm instead

Signed-off-by: trivernis <trivernis@protonmail.com>
pull/53/head
trivernis 2 years ago
parent 9819b58243
commit 26c7df783b
Signed by: Trivernis
GPG Key ID: DFFFCC2C7A02DB45

1249
Cargo.lock generated

File diff suppressed because it is too large Load Diff

@ -1,5 +1,5 @@
[workspace] [workspace]
members=["bot-coreutils", "bot-database", "."] members=["bot-coreutils", "bot-database", "bot-database/migration", "."]
[package] [package]
name = "tobi-rs" name = "tobi-rs"

@ -10,8 +10,11 @@ edition = "2018"
dotenv = "0.15.0" dotenv = "0.15.0"
chrono = "0.4.19" chrono = "0.4.19"
thiserror = "1.0.30" thiserror = "1.0.30"
diesel = { version = "1.4.8", features = ["postgres", "r2d2", "chrono"] } tracing = "0.1.34"
log = "0.4.16"
diesel_migrations = "1.4.0" [dependencies.sea-orm]
r2d2 = "0.8.9" version = "0.7.1"
tokio-diesel = {git = "https://github.com/Trivernis/tokio-diesel"} features = ["runtime-tokio-native-tls", "sqlx-postgres"]
[dependencies.migration]
path = "./migration"

@ -1,5 +0,0 @@
# For documentation on how to configure this file,
# see diesel.rs/guides/configuring-diesel-cli
[print_schema]
file = "src/schema.rs"

@ -0,0 +1,12 @@
[package]
name = "migration"
version = "0.1.0"
edition = "2021"
publish = false
[lib]
name = "migration"
path = "src/lib.rs"
[dependencies]
sea-schema = { version = "^0.7.0", default-features = false, features = [ "migration", "debug-print" ] }

@ -0,0 +1,37 @@
# Running Migrator CLI
- Apply all pending migrations
```sh
cargo run
```
```sh
cargo run -- up
```
- Apply first 10 pending migrations
```sh
cargo run -- up -n 10
```
- Rollback last applied migrations
```sh
cargo run -- down
```
- Rollback last 10 applied migrations
```sh
cargo run -- down -n 10
```
- Drop all tables from the database, then reapply all migrations
```sh
cargo run -- fresh
```
- Rollback all applied migrations, then reapply all migrations
```sh
cargo run -- refresh
```
- Rollback all applied migrations
```sh
cargo run -- reset
```
- Check the status of all migrations
```sh
cargo run -- status
```

@ -0,0 +1,16 @@
pub use sea_schema::migration::prelude::*;
mod m20220029_164527_change_timestamp_format;
mod m20220101_000001_create_table;
pub struct Migrator;
#[async_trait::async_trait]
impl MigratorTrait for Migrator {
fn migrations() -> Vec<Box<dyn MigrationTrait>> {
vec![
Box::new(m20220029_164527_change_timestamp_format::Migration),
Box::new(m20220101_000001_create_table::Migration),
]
}
}

@ -0,0 +1,82 @@
use crate::{DbErr, Table};
use sea_schema::migration::prelude::*;
#[derive(Iden)]
pub enum Statistics {
Table,
ExecutedAt,
}
#[derive(Iden)]
pub enum EphemeralMessages {
Table,
Timeout,
}
pub struct Migration;
impl MigrationName for Migration {
fn name(&self) -> &str {
"m20220029_164527_change_timestamp_format"
}
}
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.alter_table(
Table::alter()
.table(Statistics::Table)
.modify_column(
ColumnDef::new(Statistics::ExecutedAt)
.timestamp_with_time_zone()
.not_null(),
)
.to_owned(),
)
.await?;
manager
.alter_table(
Table::alter()
.table(EphemeralMessages::Table)
.modify_column(
ColumnDef::new(EphemeralMessages::Timeout)
.timestamp_with_time_zone()
.not_null(),
)
.to_owned(),
)
.await?;
Ok(())
}
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.alter_table(
Table::alter()
.table(Statistics::Table)
.modify_column(
ColumnDef::new(Statistics::ExecutedAt)
.timestamp()
.not_null(),
)
.to_owned(),
)
.await?;
manager
.alter_table(
Table::alter()
.table(EphemeralMessages::Table)
.modify_column(
ColumnDef::new(EphemeralMessages::Timeout)
.timestamp()
.not_null(),
)
.to_owned(),
)
.await?;
Ok(())
}
}

@ -0,0 +1,288 @@
use sea_query::Table;
use sea_schema::migration::prelude::*;
pub struct Migration;
#[derive(Iden)]
pub enum EphemeralMessages {
Table,
ChannelId,
MessageId,
Timeout,
}
#[derive(Iden)]
pub enum GuildPlaylists {
Table,
GuildId,
Name,
Url,
}
#[derive(Iden)]
pub enum GuildSettings {
Table,
GuildId,
Key,
Value,
}
#[derive(Iden)]
pub enum Media {
Table,
Id,
Category,
Name,
Url,
}
#[derive(Iden)]
pub enum Statistics {
Table,
Id,
Version,
Command,
ExecutedAt,
Success,
ErrorMsg,
}
#[derive(Iden)]
pub enum YoutubeSongs {
Table,
Id,
SpotifyId,
Artist,
Title,
Album,
Url,
Score,
}
impl MigrationName for Migration {
fn name(&self) -> &str {
"m20220101_000001_create_table"
}
}
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager.create_table(ephemeral_messages()).await?;
manager.create_table(guild_playlists()).await?;
manager.create_table(guild_settings()).await?;
manager.create_table(media()).await?;
manager.create_table(statistics()).await?;
manager.create_table(youtube_songs()).await?;
Ok(())
}
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.drop_table(Table::drop().table(EphemeralMessages::Table).to_owned())
.await?;
manager
.drop_table(Table::drop().table(GuildPlaylists::Table).to_owned())
.await?;
manager
.drop_table(Table::drop().table(GuildSettings::Table).to_owned())
.await?;
manager
.drop_table(Table::drop().table(Media::Table).to_owned())
.await?;
manager
.drop_table(Table::drop().table(Statistics::Table).to_owned())
.await?;
manager
.drop_table(Table::drop().table(YoutubeSongs::Table).to_owned())
.await?;
Ok(())
}
}
fn ephemeral_messages() -> TableCreateStatement {
Table::create()
.table(EphemeralMessages::Table)
.if_not_exists()
.col(
ColumnDef::new(EphemeralMessages::ChannelId)
.big_integer()
.not_null(),
)
.col(
ColumnDef::new(EphemeralMessages::MessageId)
.big_integer()
.not_null(),
)
.col(
ColumnDef::new(EphemeralMessages::Timeout)
.timestamp()
.not_null(),
)
.primary_key(
Index::create()
.col(EphemeralMessages::ChannelId)
.col(EphemeralMessages::MessageId),
)
.to_owned()
}
fn guild_playlists() -> TableCreateStatement {
Table::create()
.table(GuildPlaylists::Table)
.if_not_exists()
.col(
ColumnDef::new(GuildPlaylists::GuildId)
.big_integer()
.not_null(),
)
.col(
ColumnDef::new(GuildPlaylists::Name)
.string_len(255)
.not_null(),
)
.col(
ColumnDef::new(GuildPlaylists::Url)
.string_len(1204)
.not_null(),
)
.primary_key(
Index::create()
.col(GuildPlaylists::GuildId)
.col(GuildPlaylists::Name),
)
.to_owned()
}
fn guild_settings() -> TableCreateStatement {
Table::create()
.table(GuildSettings::Table)
.if_not_exists()
.col(
ColumnDef::new(GuildSettings::GuildId)
.big_integer()
.not_null(),
)
.col(
ColumnDef::new(GuildSettings::Key)
.string_len(255)
.not_null(),
)
.col(
ColumnDef::new(GuildSettings::Value)
.string_len(1024)
.not_null(),
)
.primary_key(
Index::create()
.col(GuildSettings::GuildId)
.col(GuildSettings::Key),
)
.to_owned()
}
fn media() -> TableCreateStatement {
Table::create()
.table(Media::Table)
.if_not_exists()
.col(
ColumnDef::new(Media::Id)
.big_integer()
.auto_increment()
.not_null()
.primary_key(),
)
.col(ColumnDef::new(Media::Category).string_len(128))
.col(ColumnDef::new(Media::Name).string_len(128))
.col(ColumnDef::new(Media::Url).string_len(128))
.index(
Index::create()
.unique()
.col(Media::Category)
.col(Media::Name),
)
.to_owned()
}
fn statistics() -> TableCreateStatement {
Table::create()
.table(Statistics::Table)
.if_not_exists()
.col(
ColumnDef::new(Statistics::Id)
.big_integer()
.auto_increment()
.primary_key(),
)
.col(
ColumnDef::new(Statistics::Version)
.string_len(32)
.not_null(),
)
.col(
ColumnDef::new(Statistics::Command)
.string_len(255)
.not_null(),
)
.col(
ColumnDef::new(Statistics::ExecutedAt)
.timestamp()
.not_null(),
)
.col(
ColumnDef::new(Statistics::Success)
.boolean()
.not_null()
.default(true),
)
.col(ColumnDef::new(Statistics::ErrorMsg).string())
.to_owned()
}
fn youtube_songs() -> TableCreateStatement {
Table::create()
.table(YoutubeSongs::Table)
.if_not_exists()
.col(
ColumnDef::new(YoutubeSongs::Id)
.big_integer()
.primary_key()
.auto_increment(),
)
.col(
ColumnDef::new(YoutubeSongs::SpotifyId)
.string_len(255)
.not_null(),
)
.col(
ColumnDef::new(YoutubeSongs::Artist)
.string_len(128)
.not_null(),
)
.col(
ColumnDef::new(YoutubeSongs::Title)
.string_len(255)
.not_null(),
)
.col(
ColumnDef::new(YoutubeSongs::Album)
.string_len(255)
.not_null(),
)
.col(ColumnDef::new(YoutubeSongs::Url).string_len(128).not_null())
.col(
ColumnDef::new(YoutubeSongs::Score)
.integer()
.default(0)
.not_null(),
)
.index(
Index::create()
.unique()
.col(YoutubeSongs::SpotifyId)
.col(YoutubeSongs::Url),
)
.to_owned()
}

@ -0,0 +1,7 @@
use migration::Migrator;
use sea_schema::migration::prelude::*;
#[async_std::main]
async fn main() {
cli::run_cli(Migrator).await;
}

@ -1,6 +0,0 @@
-- This file was automatically created by Diesel to setup helper functions
-- and other internal bookkeeping. This file is safe to edit, any future
-- changes will be added to existing projects as new migrations.
DROP FUNCTION IF EXISTS diesel_manage_updated_at(_tbl regclass);
DROP FUNCTION IF EXISTS diesel_set_updated_at();

@ -1,36 +0,0 @@
-- This file was automatically created by Diesel to setup helper functions
-- and other internal bookkeeping. This file is safe to edit, any future
-- changes will be added to existing projects as new migrations.
-- Sets up a trigger for the given table to automatically set a column called
-- `updated_at` whenever the row is modified (unless `updated_at` was included
-- in the modified columns)
--
-- # Example
--
-- ```sql
-- CREATE TABLE users (id SERIAL PRIMARY KEY, updated_at TIMESTAMP NOT NULL DEFAULT NOW());
--
-- SELECT diesel_manage_updated_at('users');
-- ```
CREATE OR REPLACE FUNCTION diesel_manage_updated_at(_tbl regclass) RETURNS VOID AS $$
BEGIN
EXECUTE format('CREATE TRIGGER set_updated_at BEFORE UPDATE ON %s
FOR EACH ROW EXECUTE PROCEDURE diesel_set_updated_at()', _tbl);
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION diesel_set_updated_at() RETURNS trigger AS $$
BEGIN
IF (
NEW IS DISTINCT FROM OLD AND
NEW.updated_at IS NOT DISTINCT FROM OLD.updated_at
) THEN
NEW.updated_at := current_timestamp;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;

@ -1,2 +0,0 @@
-- This file should undo anything in `up.sql`
DROP TABLE guild_settings;

@ -1,6 +0,0 @@
CREATE TABLE guild_settings (
guild_id BIGINT NOT NULL,
key VARCHAR(255) NOT NULL,
value VARCHAR(1024),
PRIMARY KEY (guild_id, key)
);

@ -1,2 +0,0 @@
-- This file should undo anything in `up.sql`
DROP TABLE guild_playlists;

@ -1,7 +0,0 @@
-- Your SQL goes here
CREATE TABLE guild_playlists (
guild_id BIGINT NOT NULL,
name VARCHAR(255) NOT NULL,
url VARCHAR(1024) NOT NULL,
PRIMARY KEY (guild_id, name)
)

@ -1,2 +0,0 @@
-- This file should undo anything in `up.sql`
DROP TABLE gifs;

@ -1,8 +0,0 @@
-- Your SQL goes here
CREATE TABLE gifs (
id BIGSERIAL PRIMARY KEY ,
category VARCHAR(128),
name VARCHAR(128),
url VARCHAR(128) NOT NULL,
UNIQUE (category, name)
)

@ -1,2 +0,0 @@
-- This file should undo anything in `up.sql`
DROP TABLE statistics;

@ -1,9 +0,0 @@
-- Your SQL goes here
CREATE TABLE statistics (
id BIGSERIAL PRIMARY KEY,
version VARCHAR(32) NOT NULL,
command VARCHAR(255) NOT NULL,
executed_at TIMESTAMP NOT NULL,
success BOOLEAN NOT NULL DEFAULT TRUE,
error_msg TEXT
)

@ -1,2 +0,0 @@
-- This file should undo anything in `up.sql`
DROP TABLE youtube_songs;

@ -1,11 +0,0 @@
-- Your SQL goes here
CREATE TABLE youtube_songs (
id BIGSERIAL PRIMARY KEY,
spotify_id VARCHAR(255) NOT NULL,
artist VARCHAR(128) NOT NULL,
title VARCHAR(255) NOT NULL,
album VARCHAR(255) NOT NULL,
url VARCHAR(128) NOT NULL,
score INTEGER DEFAULT 0 NOT NULL,
UNIQUE (spotify_id, url)
)

@ -1,2 +0,0 @@
-- This file should undo anything in `up.sql`
DROP TABLE ephemeral_messages;

@ -1,7 +0,0 @@
-- Your SQL goes here
CREATE TABLE ephemeral_messages (
channel_id BIGINT NOT NULL,
message_id BIGINT NOT NULL,
timeout TIMESTAMP NOT NULL,
PRIMARY KEY (channel_id, message_id)
)

@ -1,2 +0,0 @@
-- This file should undo anything in `up.sql`
ALTER TABLE media RENAME TO gifs;

@ -1,2 +0,0 @@
-- Your SQL goes here
ALTER TABLE gifs RENAME TO media;

@ -1,56 +1,49 @@
use sea_orm::ActiveValue::Set;
use std::time::SystemTime; use std::time::SystemTime;
use diesel::prelude::*; use crate::entity::ephemeral_messages;
use diesel::{delete, insert_into};
use tokio_diesel::*;
use crate::error::DatabaseResult; use crate::error::DatabaseResult;
use crate::models::*; use sea_orm::prelude::*;
use crate::schema::*;
use crate::Database;
impl Database { impl super::BotDatabase {
/// Adds a command statistic to the database /// Adds a command statistic to the database
#[tracing::instrument(level = "debug", skip(self))]
pub async fn add_ephemeral_message( pub async fn add_ephemeral_message(
&self, &self,
channel_id: u64, channel_id: u64,
message_id: u64, message_id: u64,
timeout: SystemTime, timeout: SystemTime,
) -> DatabaseResult<()> { ) -> DatabaseResult<()> {
use ephemeral_messages::dsl; let model = ephemeral_messages::ActiveModel {
insert_into(dsl::ephemeral_messages) channel_id: Set(channel_id as i64),
.values(EphemeralMessageInsert { message_id: Set(message_id as i64),
channel_id: channel_id as i64, timeout: Set(DateTimeLocal::from(timeout).into()),
message_id: message_id as i64, ..Default::default()
timeout, };
}) model.insert(&self.db).await?;
.execute_async(&self.pool)
.await?;
Ok(()) Ok(())
} }
/// Returns a vec of all ephemeral messages /// Returns a vec of all ephemeral messages
pub async fn get_ephemeral_messages(&self) -> DatabaseResult<Vec<EphemeralMessage>> { #[tracing::instrument(level = "debug", skip(self))]
use ephemeral_messages::dsl; pub async fn get_ephemeral_messages(&self) -> DatabaseResult<Vec<ephemeral_messages::Model>> {
let messages: Vec<EphemeralMessage> = dsl::ephemeral_messages let messages = ephemeral_messages::Entity::find().all(&self.db).await?;
.load_async::<EphemeralMessage>(&self.pool)
.await?;
Ok(messages) Ok(messages)
} }
/// Deletes a single ephemeral message /// Deletes a single ephemeral message
#[tracing::instrument(level = "debug", skip(self))]
pub async fn delete_ephemeral_message( pub async fn delete_ephemeral_message(
&self, &self,
channel_id: i64, channel_id: i64,
message_id: i64, message_id: i64,
) -> DatabaseResult<()> { ) -> DatabaseResult<()> {
use ephemeral_messages::dsl; ephemeral_messages::Entity::delete_many()
delete(dsl::ephemeral_messages) .filter(ephemeral_messages::Column::ChannelId.eq(channel_id))
.filter(dsl::channel_id.eq(channel_id)) .filter(ephemeral_messages::Column::MessageId.eq(message_id))
.filter(dsl::message_id.eq(message_id)) .exec(&self.db)
.execute_async(&self.pool)
.await?; .await?;
Ok(()) Ok(())

@ -1,65 +1,54 @@
use diesel::insert_into; use crate::entity::guild_playlists;
use diesel::prelude::*;
use tokio_diesel::*;
use crate::error::DatabaseResult; use crate::error::DatabaseResult;
use crate::models::*; use sea_orm::prelude::*;
use crate::schema::*; use sea_orm::ActiveValue::Set;
use crate::Database;
impl Database { impl super::BotDatabase {
/// Returns a list of all guild playlists /// Returns a list of all guild playlists
pub async fn get_guild_playlists(&self, guild_id: u64) -> DatabaseResult<Vec<GuildPlaylist>> { #[tracing::instrument(level = "debug", skip(self))]
use guild_playlists::dsl; pub async fn get_guild_playlists(
log::debug!("Retrieving guild playlists for guild {}", guild_id); &self,
guild_id: u64,
let playlists: Vec<GuildPlaylist> = dsl::guild_playlists ) -> DatabaseResult<Vec<guild_playlists::Model>> {
.filter(dsl::guild_id.eq(guild_id as i64)) let playlists = guild_playlists::Entity::find()
.load_async::<GuildPlaylist>(&self.pool) .filter(guild_playlists::Column::GuildId.eq(guild_id))
.all(&self.db)
.await?; .await?;
Ok(playlists) Ok(playlists)
} }
/// Returns a guild playlist by name /// Returns a guild playlist by name
#[tracing::instrument(level = "debug", skip(self))]
pub async fn get_guild_playlist( pub async fn get_guild_playlist(
&self, &self,
guild_id: u64, guild_id: u64,
name: String, name: String,
) -> DatabaseResult<Option<GuildPlaylist>> { ) -> DatabaseResult<Option<guild_playlists::Model>> {
use guild_playlists::dsl; let playlist = guild_playlists::Entity::find()
log::debug!("Retriving guild playlist '{}' for guild {}", name, guild_id); .filter(guild_playlists::Column::GuildId.eq(guild_id))
.filter(guild_playlists::Column::Name.eq(name))
let playlists: Vec<GuildPlaylist> = dsl::guild_playlists .one(&self.db)
.filter(dsl::guild_id.eq(guild_id as i64))
.filter(dsl::name.eq(name))
.load_async::<GuildPlaylist>(&self.pool)
.await?; .await?;
Ok(playlists.into_iter().next()) Ok(playlist)
} }
/// Adds a new playlist to the database overwriting the old one /// Adds a new playlist to the database overwriting the old one
#[tracing::instrument(level = "debug", skip(self))]
pub async fn add_guild_playlist( pub async fn add_guild_playlist(
&self, &self,
guild_id: u64, guild_id: u64,
name: String, name: String,
url: String, url: String,
) -> DatabaseResult<()> { ) -> DatabaseResult<()> {
use guild_playlists::dsl; let model = guild_playlists::ActiveModel {
log::debug!("Inserting guild playlist '{}' for guild {}", name, guild_id); guild_id: Set(guild_id as i64),
name: Set(name),
insert_into(dsl::guild_playlists) url: Set(url),
.values(GuildPlaylistInsert { ..Default::default()
guild_id: guild_id as i64, };
name: name.clone(), model.insert(&self.db).await?;
url: url.clone(),
})
.on_conflict((dsl::guild_id, dsl::name))
.do_update()
.set(dsl::url.eq(url))
.execute_async(&self.pool)
.await?;
Ok(()) Ok(())
} }

@ -1,53 +1,46 @@
use sea_orm::ActiveValue::Set;
use std::any; use std::any;
use std::fmt::Debug; use std::fmt::Debug;
use std::str::FromStr; use std::str::FromStr;
use diesel::prelude::*; use crate::entity::guild_settings;
use diesel::{delete, insert_into};
use tokio_diesel::*;
use crate::error::DatabaseResult; use crate::error::DatabaseResult;
use crate::models::*; use sea_orm::prelude::*;
use crate::schema::*;
use crate::Database;
impl Database { impl super::BotDatabase {
/// Returns a guild setting from the database /// Returns a guild setting from the database
pub async fn get_guild_setting<T: 'static>( #[tracing::instrument(level = "debug", skip(self))]
pub async fn get_guild_setting<T: 'static, S: AsRef<str> + Debug>(
&self, &self,
guild_id: u64, guild_id: u64,
key: String, key: S,
) -> DatabaseResult<Option<T>> ) -> DatabaseResult<Option<T>>
where where
T: FromStr, T: FromStr,
{ {
use guild_settings::dsl; let setting = guild_settings::Entity::find()
log::debug!("Retrieving setting '{}' for guild {}", key, guild_id); .filter(guild_settings::Column::GuildId.eq(guild_id as i64))
.filter(guild_settings::Column::Key.eq(key.as_ref()))
let entries: Vec<GuildSetting> = dsl::guild_settings .one(&self.db)
.filter(dsl::guild_id.eq(guild_id as i64))
.filter(dsl::key.eq(key))
.load_async::<GuildSetting>(&self.pool)
.await?; .await?;
log::trace!("Result is {:?}", entries); if let Some(setting) = setting {
if let Some(first) = entries.first() {
if any::TypeId::of::<T>() == any::TypeId::of::<bool>() { if any::TypeId::of::<T>() == any::TypeId::of::<bool>() {
Ok(first Ok(setting
.value .value
.clone() .clone()
.unwrap_or("false".to_string()) .unwrap_or("false".to_string())
.parse::<T>() .parse::<T>()
.ok()) .ok())
} else { } else {
Ok(first.value.clone().and_then(|v| v.parse::<T>().ok())) Ok(setting.value.clone().and_then(|v| v.parse::<T>().ok()))
} }
} else { } else {
return Ok(None); Ok(None)
} }
} }
/// Upserting a guild setting /// Upserting a guild setting
#[tracing::instrument(level = "debug", skip(self))]
pub async fn set_guild_setting<T>( pub async fn set_guild_setting<T>(
&self, &self,
guild_id: u64, guild_id: u64,
@ -55,33 +48,38 @@ impl Database {
value: T, value: T,
) -> DatabaseResult<()> ) -> DatabaseResult<()>
where where
T: ToString + Debug, T: 'static + ToString + FromStr + Debug,
{ {
use guild_settings::dsl; let model = guild_settings::ActiveModel {
log::debug!("Setting '{}' to '{:?}' for guild {}", key, value, guild_id); guild_id: Set(guild_id as i64),
key: Set(key.clone()),
insert_into(dsl::guild_settings) value: Set(Some(value.to_string())),
.values(GuildSettingInsert { ..Default::default()
guild_id: guild_id as i64, };
key: key.to_string(), if self
value: value.to_string(), .get_guild_setting::<T, _>(guild_id, &key)
}) .await?
.on_conflict((dsl::guild_id, dsl::key)) .is_some()
.do_update() {
.set(dsl::value.eq(value.to_string())) model.update(&self.db).await?;
.execute_async(&self.pool) } else {
.await?; model.insert(&self.db).await?;
}
Ok(()) Ok(())
} }
/// Deletes a guild setting /// Deletes a guild setting
pub async fn delete_guild_setting(&self, guild_id: u64, key: String) -> DatabaseResult<()> { #[tracing::instrument(level = "debug", skip(self))]
use guild_settings::dsl; pub async fn delete_guild_setting<S: AsRef<str> + Debug>(
delete(dsl::guild_settings) &self,
.filter(dsl::guild_id.eq(guild_id as i64)) guild_id: u64,
.filter(dsl::key.eq(key)) key: S,
.execute_async(&self.pool) ) -> DatabaseResult<()> {
guild_settings::Entity::delete_many()
.filter(guild_settings::Column::GuildId.eq(guild_id))
.filter(guild_settings::Column::Key.eq(key.as_ref()))
.exec(&self.db)
.await?; .await?;
Ok(()) Ok(())

@ -1,59 +1,47 @@
use diesel::insert_into; use crate::entity::media;
use diesel::prelude::*;
use tokio_diesel::*;
use crate::error::DatabaseResult; use crate::error::DatabaseResult;
use crate::models::*; use sea_orm::prelude::*;
use crate::schema::*; use sea_orm::ActiveValue::Set;
use crate::Database; use std::fmt::Debug;
impl Database { impl super::BotDatabase {
/// Returns a list of all gifs in the database /// Returns a list of all gifs in the database
pub async fn get_all_media(&self) -> DatabaseResult<Vec<Media>> { #[tracing::instrument(level = "debug", skip(self))]
use media::dsl; pub async fn get_all_media(&self) -> DatabaseResult<Vec<media::Model>> {
log::debug!("Loading all gifs from the database"); let entries = media::Entity::find().all(&self.db).await?;
let gifs: Vec<Media> = dsl::media.load_async::<Media>(&self.pool).await?; Ok(entries)
Ok(gifs)
} }
/// Returns a list of gifs by assigned category /// Returns a list of gifs by assigned category
pub async fn get_media_by_category<S: AsRef<str> + 'static>( #[tracing::instrument(level = "debug", skip(self))]
pub async fn get_media_by_category<S: AsRef<str> + 'static + Debug>(
&self, &self,
category: S, category: S,
) -> DatabaseResult<Vec<Media>> { ) -> DatabaseResult<Vec<media::Model>> {
use media::dsl; let entries = media::Entity::find()
log::debug!("Searching for gifs in category '{}'", category.as_ref()); .filter(media::Column::Category.eq(category.as_ref()))
.all(&self.db)
let gifs: Vec<Media> = dsl::media
.filter(dsl::category.eq(category.as_ref()))
.load_async::<Media>(&self.pool)
.await?; .await?;
Ok(gifs)
Ok(entries)
} }
/// Adds a gif to the database /// Adds a gif to the database
#[tracing::instrument(level = "debug", skip(self))]
pub async fn add_media( pub async fn add_media(
&self, &self,
url: &str, url: String,
category: Option<String>, category: Option<String>,
name: Option<String>, name: Option<String>,
) -> DatabaseResult<()> { ) -> DatabaseResult<()> {
use media::dsl; let model = media::ActiveModel {
log::debug!( url: Set(url),
"Inserting gif with url '{}' and name {:?} and category {:?}", category: Set(category),
url, name: Set(name),
name, ..Default::default()
category };
); model.insert(&self.db).await?;
insert_into(dsl::media)
.values(MediaInsert {
url: url.to_string(),
name,
category,
})
.execute_async(&self.pool)
.await?;
Ok(()) Ok(())
} }

@ -2,11 +2,10 @@ pub use ephemeral_messages::*;
pub use guild_playlists::*; pub use guild_playlists::*;
pub use guild_playlists::*; pub use guild_playlists::*;
pub use media::*; pub use media::*;
use sea_orm::DatabaseConnection;
pub use statistics::*; pub use statistics::*;
pub use youtube_songs::*; pub use youtube_songs::*;
use crate::PoolConnection;
mod ephemeral_messages; mod ephemeral_messages;
mod guild_playlists; mod guild_playlists;
mod guild_settings; mod guild_settings;
@ -15,16 +14,12 @@ mod statistics;
mod youtube_songs; mod youtube_songs;
#[derive(Clone)] #[derive(Clone)]
pub struct Database { pub struct BotDatabase {
pool: PoolConnection, db: DatabaseConnection,
} }
unsafe impl Send for Database {} impl BotDatabase {
pub fn new(db: DatabaseConnection) -> Self {
unsafe impl Sync for Database {} Self { db }
impl Database {
pub fn new(pool: PoolConnection) -> Self {
Self { pool }
} }
} }

@ -1,50 +1,49 @@
use crate::entity::statistics;
use crate::error::DatabaseResult;
use sea_orm::prelude::*;
use sea_orm::ActiveValue::Set;
use sea_orm::{FromQueryResult, QuerySelect};
use std::time::SystemTime; use std::time::SystemTime;
use diesel::dsl::count; #[derive(FromQueryResult)]
use diesel::insert_into; struct CommandCount {
use diesel::prelude::*; count: i64,
use tokio_diesel::*; }
use crate::error::DatabaseResult;
use crate::models::*;
use crate::schema::*;
use crate::Database;
impl Database { impl super::BotDatabase {
/// Adds a command statistic to the database /// Adds a command statistic to the database
#[tracing::instrument(level = "debug", skip(self))]
pub async fn add_statistic( pub async fn add_statistic(
&self, &self,
version: &str, version: String,
command: &str, command: String,
executed_at: SystemTime, executed_at: SystemTime,
success: bool, success: bool,
error_msg: Option<String>, error_msg: Option<String>,
) -> DatabaseResult<()> { ) -> DatabaseResult<()> {
use statistics::dsl; let model = statistics::ActiveModel {
log::trace!("Adding statistic to database"); version: Set(version),
insert_into(dsl::statistics) command: Set(command),
.values(StatisticInsert { executed_at: Set(DateTimeLocal::from(executed_at).into()),
version: version.to_string(), success: Set(success),
command: command.to_string(), error_msg: Set(error_msg),
executed_at, ..Default::default()
success, };
error_msg, model.insert(&self.db).await?;
})
.execute_async(&self.pool)
.await?;
Ok(()) Ok(())
} }
/// Returns the total number of commands executed /// Returns the total number of commands executed
#[tracing::instrument(level = "debug", skip(self))]
pub async fn get_total_commands_statistic(&self) -> DatabaseResult<u64> { pub async fn get_total_commands_statistic(&self) -> DatabaseResult<u64> {
use statistics::dsl; let total_count: Option<CommandCount> = statistics::Entity::find()
log::trace!("Querying total number of commands"); .select_only()
let total_count: i64 = dsl::statistics .column_as(statistics::Column::Id.count(), "count")
.select(count(dsl::id)) .into_model::<CommandCount>()
.first_async::<i64>(&self.pool) .one(&self.db)
.await?; .await?;
Ok(total_count as u64) Ok(total_count.unwrap().count as u64)
} }
} }

@ -1,69 +1,56 @@
use diesel::prelude::*; use crate::entity::youtube_songs;
use diesel::{delete, insert_into};
use tokio_diesel::*;
use crate::error::DatabaseResult; use crate::error::DatabaseResult;
use crate::models::*; use sea_orm::prelude::*;
use crate::schema::*; use sea_orm::ActiveValue::Set;
use crate::Database;
impl Database { impl super::BotDatabase {
/// Adds a song to the database or increments the score when it /// Adds a song to the database or increments the score when it
/// already exists /// already exists
#[tracing::instrument(level = "debug", skip(self))]
pub async fn add_song( pub async fn add_song(
&self, &self,
spotify_id: &str, spotify_id: String,
artist: &str, artist: String,
title: &str, title: String,
album: &str, album: String,
url: &str, url: String,
) -> DatabaseResult<()> { ) -> DatabaseResult<()> {
use youtube_songs::dsl; if let Some(model) = self.get_song(&spotify_id).await? {
log::debug!( let mut active_model: youtube_songs::ActiveModel = model.into();
"Inserting/Updating song in database spotify_id: '{}' artist: '{}', title: '{}', album: '{}', url: '{}'", active_model.score = Set(active_model.score.unwrap() + 1);
spotify_id, active_model.update(&self.db).await?;
artist, } else {
title, let model = youtube_songs::ActiveModel {
album, spotify_id: Set(spotify_id),
url, artist: Set(artist),
); title: Set(title),
album: Set(album),
insert_into(dsl::youtube_songs) url: Set(url),
.values(YoutubeSongInsert { ..Default::default()
spotify_id: spotify_id.to_string(), };
artist: artist.to_string(), model.insert(&self.db).await?;
title: title.to_string(), }
album: album.to_string(),
url: url.to_string(),
})
.on_conflict((dsl::spotify_id, dsl::url))
.do_update()
.set(dsl::score.eq(dsl::score + 1))
.execute_async(&self.pool)
.await?;
Ok(()) Ok(())
} }
/// Returns the song with the best score for the given query /// Returns the song with the best score for the given query
pub async fn get_song(&self, spotify_id: &str) -> DatabaseResult<Option<YoutubeSong>> { #[tracing::instrument(level = "debug", skip(self))]
use youtube_songs::dsl; pub async fn get_song(&self, spotify_id: &str) -> DatabaseResult<Option<youtube_songs::Model>> {
let songs: Vec<YoutubeSong> = dsl::youtube_songs let song = youtube_songs::Entity::find()
.filter(dsl::spotify_id.eq(spotify_id)) .filter(youtube_songs::Column::SpotifyId.eq(spotify_id))
.order(dsl::score.desc()) .one(&self.db)
.limit(1)
.load_async::<YoutubeSong>(&self.pool)
.await?; .await?;
Ok(songs.into_iter().next()) Ok(song)
} }
/// Deletes a song from the database /// Deletes a song from the database
#[tracing::instrument(level = "debug", skip(self))]
pub async fn delete_song(&self, id: i64) -> DatabaseResult<()> { pub async fn delete_song(&self, id: i64) -> DatabaseResult<()> {
use youtube_songs::dsl; youtube_songs::Entity::delete_many()
delete(dsl::youtube_songs) .filter(youtube_songs::Column::Id.eq(id))
.filter(dsl::id.eq(id)) .exec(&self.db)
.execute_async(&self.pool)
.await?; .await?;
Ok(()) Ok(())

@ -0,0 +1,24 @@
//! SeaORM Entity. Generated by sea-orm-codegen 0.7.0
use sea_orm::entity::prelude::*;
#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
#[sea_orm(table_name = "ephemeral_messages")]
pub struct Model {
#[sea_orm(primary_key, auto_increment = false)]
pub channel_id: i64,
#[sea_orm(primary_key, auto_increment = false)]
pub message_id: i64,
pub timeout: DateTimeWithTimeZone,
}
#[derive(Copy, Clone, Debug, EnumIter)]
pub enum Relation {}
impl RelationTrait for Relation {
fn def(&self) -> RelationDef {
panic!("No RelationDef")
}
}
impl ActiveModelBehavior for ActiveModel {}

@ -0,0 +1,24 @@
//! SeaORM Entity. Generated by sea-orm-codegen 0.7.0
use sea_orm::entity::prelude::*;
#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
#[sea_orm(table_name = "guild_playlists")]
pub struct Model {
#[sea_orm(primary_key, auto_increment = false)]
pub guild_id: i64,
#[sea_orm(primary_key, auto_increment = false)]
pub name: String,
pub url: String,
}
#[derive(Copy, Clone, Debug, EnumIter)]
pub enum Relation {}
impl RelationTrait for Relation {
fn def(&self) -> RelationDef {
panic!("No RelationDef")
}
}
impl ActiveModelBehavior for ActiveModel {}

@ -0,0 +1,24 @@
//! SeaORM Entity. Generated by sea-orm-codegen 0.7.0
use sea_orm::entity::prelude::*;
#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
#[sea_orm(table_name = "guild_settings")]
pub struct Model {
#[sea_orm(primary_key, auto_increment = false)]
pub guild_id: i64,
#[sea_orm(primary_key, auto_increment = false)]
pub key: String,
pub value: Option<String>,
}
#[derive(Copy, Clone, Debug, EnumIter)]
pub enum Relation {}
impl RelationTrait for Relation {
fn def(&self) -> RelationDef {
panic!("No RelationDef")
}
}
impl ActiveModelBehavior for ActiveModel {}

@ -0,0 +1,24 @@
//! SeaORM Entity. Generated by sea-orm-codegen 0.7.0
use sea_orm::entity::prelude::*;
#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
#[sea_orm(table_name = "media")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i64,
pub category: Option<String>,
pub name: Option<String>,
pub url: String,
}
#[derive(Copy, Clone, Debug, EnumIter)]
pub enum Relation {}
impl RelationTrait for Relation {
fn def(&self) -> RelationDef {
panic!("No RelationDef")
}
}
impl ActiveModelBehavior for ActiveModel {}

@ -0,0 +1,10 @@
//! SeaORM Entity. Generated by sea-orm-codegen 0.7.0
pub mod prelude;
pub mod ephemeral_messages;
pub mod guild_playlists;
pub mod guild_settings;
pub mod media;
pub mod statistics;
pub mod youtube_songs;

@ -0,0 +1,8 @@
//! SeaORM Entity. Generated by sea-orm-codegen 0.7.0
pub use super::ephemeral_messages::Entity as EphemeralMessages;
pub use super::guild_playlists::Entity as GuildPlaylists;
pub use super::guild_settings::Entity as GuildSettings;
pub use super::media::Entity as Media;
pub use super::statistics::Entity as Statistics;
pub use super::youtube_songs::Entity as YoutubeSongs;

@ -0,0 +1,27 @@
//! SeaORM Entity. Generated by sea-orm-codegen 0.7.0
use sea_orm::entity::prelude::*;
#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
#[sea_orm(table_name = "statistics")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i64,
pub version: String,
pub command: String,
pub executed_at: DateTimeWithTimeZone,
pub success: bool,
#[sea_orm(column_type = "Text", nullable)]
pub error_msg: Option<String>,
}
#[derive(Copy, Clone, Debug, EnumIter)]
pub enum Relation {}
impl RelationTrait for Relation {
fn def(&self) -> RelationDef {
panic!("No RelationDef")
}
}
impl ActiveModelBehavior for ActiveModel {}

@ -0,0 +1,27 @@
//! SeaORM Entity. Generated by sea-orm-codegen 0.7.0
use sea_orm::entity::prelude::*;
#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
#[sea_orm(table_name = "youtube_songs")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i64,
pub spotify_id: String,
pub artist: String,
pub title: String,
pub album: String,
pub url: String,
pub score: i32,
}
#[derive(Copy, Clone, Debug, EnumIter)]
pub enum Relation {}
impl RelationTrait for Relation {
fn def(&self) -> RelationDef {
panic!("No RelationDef")
}
}
impl ActiveModelBehavior for ActiveModel {}

@ -7,20 +7,8 @@ pub enum DatabaseError {
#[error("DotEnv Error: {0}")] #[error("DotEnv Error: {0}")]
DotEnv(#[from] dotenv::Error), DotEnv(#[from] dotenv::Error),
#[error("Connection Error: {0}")] #[error("{0}")]
ConnectionError(#[from] diesel::prelude::ConnectionError), SeaOrm(#[from] sea_orm::error::DbErr),
#[error("Pool Connection Error: {0}")]
PoolConnectionError(#[from] r2d2::Error),
#[error("Migration Error: {0}")]
MigrationError(#[from] diesel_migrations::RunMigrationsError),
#[error("Result Error: {0}")]
ResultError(#[from] diesel::result::Error),
#[error("AsyncError: {0}")]
AsyncError(#[from] tokio_diesel::AsyncError),
#[error("{0}")] #[error("{0}")]
Msg(String), Msg(String),

@ -1,47 +1,31 @@
#[macro_use] use crate::error::DatabaseResult;
extern crate diesel;
#[macro_use]
extern crate diesel_migrations;
use crate::error::{DatabaseError, DatabaseResult};
use diesel::prelude::*;
use diesel::r2d2::{ConnectionManager, ManageConnection, Pool};
use std::env; use std::env;
pub mod database; pub mod database;
pub mod entity;
pub mod error; pub mod error;
pub mod models; pub mod models;
pub mod schema;
pub static VERSION: &str = env!("CARGO_PKG_VERSION"); pub static VERSION: &str = env!("CARGO_PKG_VERSION");
pub use database::Database; pub use database::BotDatabase as Database;
use migration::MigratorTrait;
type PoolConnection = Pool<ConnectionManager<PgConnection>>; use sea_orm::{ConnectOptions, Database as SeaDatabase, DatabaseConnection};
embed_migrations!("../bot-database/migrations"); #[tracing::instrument]
async fn get_connection() -> DatabaseResult<DatabaseConnection> {
fn get_connection() -> DatabaseResult<PoolConnection> {
let database_url = env::var("DATABASE_URL").expect("No DATABASE_URL in path"); let database_url = env::var("DATABASE_URL").expect("No DATABASE_URL in path");
log::debug!("Establishing database connection..."); tracing::debug!("Establishing database connection...");
let manager = ConnectionManager::<PgConnection>::new(database_url); let opt = ConnectOptions::new(database_url);
log::trace!("Connecting..."); let db = SeaDatabase::connect(opt).await?;
manager tracing::debug!("Running migrations...");
.connect() migration::Migrator::up(&db, None).await?;
.map_err(|e| DatabaseError::Msg(format!("{:?}", e)))?; tracing::debug!("Migrations finished");
log::trace!("Creating pool..."); tracing::info!("Database connection initialized");
let pool = Pool::builder().max_size(16).build(manager)?;
log::trace!("Getting one connection to run migrations..."); Ok(db)
let connection = pool.get()?;
log::debug!("Running migrations...");
embedded_migrations::run(&connection)?;
log::debug!("Migrations finished");
log::info!("Database connection initialized");
Ok(pool)
} }
pub fn get_database() -> DatabaseResult<Database> { pub async fn get_database() -> DatabaseResult<Database> {
let conn = get_connection()?; let conn = get_connection().await?;
Ok(Database::new(conn)) Ok(Database::new(conn))
} }

@ -1,94 +1,8 @@
use crate::schema::*; use super::entity;
use std::time::SystemTime;
pub use entity::ephemeral_messages::Model as EphemeralMessage;
#[derive(Queryable, Debug)] pub use entity::guild_playlists::Model as GuildPlaylist;
pub struct GuildSetting { pub use entity::guild_settings::Model as GuildSetting;
pub guild_id: i64, pub use entity::media::Model as Media;
pub key: String, pub use entity::statistics::Model as Statistic;
pub value: Option<String>, pub use entity::youtube_songs::Model as YoutubeSong;
}
#[derive(Insertable, Debug)]
#[table_name = "guild_settings"]
pub struct GuildSettingInsert {
pub guild_id: i64,
pub key: String,
pub value: String,
}
#[derive(Queryable, Debug)]
pub struct GuildPlaylist {
pub guild_id: i64,
pub name: String,
pub url: String,
}
#[derive(Insertable, Debug)]
#[table_name = "guild_playlists"]
pub struct GuildPlaylistInsert {
pub guild_id: i64,
pub name: String,
pub url: String,
}
#[derive(Queryable, Debug, Clone)]
pub struct Media {
pub id: i64,
pub category: Option<String>,
pub name: Option<String>,
pub url: String,
}
#[derive(Insertable, Debug)]
#[table_name = "media"]
pub struct MediaInsert {
pub category: Option<String>,
pub name: Option<String>,
pub url: String,
}
#[derive(Insertable, Debug)]
#[table_name = "statistics"]
pub struct StatisticInsert {
pub version: String,
pub command: String,
pub executed_at: SystemTime,
pub success: bool,
pub error_msg: Option<String>,
}
#[derive(Queryable, Debug, Clone)]
pub struct YoutubeSong {
pub id: i64,
pub spotify_id: String,
pub artist: String,
pub title: String,
pub album: String,
pub url: String,
pub score: i32,
}
#[derive(Insertable, Debug)]
#[table_name = "youtube_songs"]
pub struct YoutubeSongInsert {
pub spotify_id: String,
pub artist: String,
pub title: String,
pub album: String,
pub url: String,
}
#[derive(Queryable, Debug, Clone)]
pub struct EphemeralMessage {
pub channel_id: i64,
pub message_id: i64,
pub timeout: SystemTime,
}
#[derive(Insertable, Debug)]
#[table_name = "ephemeral_messages"]
pub struct EphemeralMessageInsert {
pub channel_id: i64,
pub message_id: i64,
pub timeout: SystemTime,
}

@ -1,64 +0,0 @@
table! {
ephemeral_messages (channel_id, message_id) {
channel_id -> Int8,
message_id -> Int8,
timeout -> Timestamp,
}
}
table! {
guild_playlists (guild_id, name) {
guild_id -> Int8,
name -> Varchar,
url -> Varchar,
}
}
table! {
guild_settings (guild_id, key) {
guild_id -> Int8,
key -> Varchar,
value -> Nullable<Varchar>,
}
}
table! {
media (id) {
id -> Int8,
category -> Nullable<Varchar>,
name -> Nullable<Varchar>,
url -> Varchar,
}
}
table! {
statistics (id) {
id -> Int8,
version -> Varchar,
command -> Varchar,
executed_at -> Timestamp,
success -> Bool,
error_msg -> Nullable<Text>,
}
}
table! {
youtube_songs (id) {
id -> Int8,
spotify_id -> Varchar,
artist -> Varchar,
title -> Varchar,
album -> Varchar,
url -> Varchar,
score -> Int4,
}
}
allow_tables_to_appear_in_same_query!(
ephemeral_messages,
guild_playlists,
guild_settings,
media,
statistics,
youtube_songs,
);

@ -24,7 +24,7 @@ use crate::utils::error::{BotError, BotResult};
pub async fn get_client() -> BotResult<Client> { pub async fn get_client() -> BotResult<Client> {
let token = env::var("BOT_TOKEN").map_err(|_| BotError::MissingToken)?; let token = env::var("BOT_TOKEN").map_err(|_| BotError::MissingToken)?;
let database = get_database()?; let database = get_database().await?;
let client = Client::builder(token) let client = Client::builder(token)
.register_rich_interactions_with(get_raw_event_handler()) .register_rich_interactions_with(get_raw_event_handler())
.event_handler(Handler) .event_handler(Handler)
@ -107,8 +107,8 @@ async fn after_hook(ctx: &Context, msg: &Message, cmd_name: &str, error: Command
let database = get_database_from_context(ctx).await; let database = get_database_from_context(ctx).await;
let _ = database let _ = database
.add_statistic( .add_statistic(
crate::VERSION, crate::VERSION.to_string(),
cmd_name, cmd_name.to_string(),
SystemTime::now(), SystemTime::now(),
error_msg.is_none(), error_msg.is_none(),
error_msg, error_msg,

@ -26,7 +26,7 @@ async fn add_media(ctx: &Context, msg: &Message, mut args: Args) -> CommandResul
let name = args.single_quoted::<String>().ok(); let name = args.single_quoted::<String>().ok();
let database = get_database_from_context(&ctx).await; let database = get_database_from_context(&ctx).await;
database.add_media(&url, category, name).await?; database.add_media(url, category, name).await?;
EphemeralMessage::create(&ctx.http, msg.channel_id, SHORT_TIMEOUT, |c| { EphemeralMessage::create(&ctx.http, msg.channel_id, SHORT_TIMEOUT, |c| {
c.reference_message(msg) c.reference_message(msg)
.content("Media entry added to the database.") .content("Media entry added to the database.")

@ -23,7 +23,7 @@ async fn get(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
if let Some(key) = args.single::<String>().ok() { if let Some(key) = args.single::<String>().ok() {
tracing::debug!("Displaying guild setting of '{}'", key); tracing::debug!("Displaying guild setting of '{}'", key);
let setting = database let setting = database
.get_guild_setting::<String>(guild.id.0, key.clone()) .get_guild_setting::<String, _>(guild.id.0, key.clone())
.await?; .await?;
match setting { match setting {
@ -47,7 +47,7 @@ async fn get(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
{ {
match database match database
.get_guild_setting::<String>(guild.id.0, key.clone()) .get_guild_setting::<String, _>(guild.id.0, key.clone())
.await? .await?
{ {
Some(value) => kv_pairs.push(format!("`{}` = `{}`", key, value)), Some(value) => kv_pairs.push(format!("`{}` = `{}`", key, value)),

@ -3,6 +3,7 @@ use serenity::client::Context;
use serenity::framework::standard::macros::group; use serenity::framework::standard::macros::group;
use serenity::framework::standard::CommandResult; use serenity::framework::standard::CommandResult;
use serenity::model::channel::Message; use serenity::model::channel::Message;
use std::fmt::Debug;
use hololive::amelia::AMELIA_COMMAND; use hololive::amelia::AMELIA_COMMAND;
use hololive::fubuki::FUBUKI_COMMAND; use hololive::fubuki::FUBUKI_COMMAND;
@ -37,7 +38,7 @@ mod theme;
pub struct Weeb; pub struct Weeb;
/// Posts a random media entry with the given category /// Posts a random media entry with the given category
async fn post_random_media<S: AsRef<str> + 'static>( async fn post_random_media<S: AsRef<str> + 'static + Debug>(
ctx: &Context, ctx: &Context,
msg: &Message, msg: &Message,
category: S, category: S,

@ -73,7 +73,7 @@ pub async fn add_youtube_song_to_database(
if let Some(id) = track.id { if let Some(id) = track.id {
database database
.add_song(&id, &artists, &track.name, &track.album.name, &url) .add_song(id, artists, track.name, track.album.name, url)
.await?; .await?;
} }

@ -38,7 +38,7 @@ pub async fn get_setting<T: 'static + FromStr>(
let data = ctx.data.read().await; let data = ctx.data.read().await;
let database = data.get::<DatabaseContainer>().unwrap(); let database = data.get::<DatabaseContainer>().unwrap();
database database
.get_guild_setting::<T>(guild_id.0, setting.to_string()) .get_guild_setting::<T, _>(guild_id.0, setting.to_string())
.await .await
.map_err(BotError::from) .map_err(BotError::from)
} }

@ -1,3 +1,4 @@
use chrono::{DateTime, FixedOffset, Local};
use std::env; use std::env;
use std::ops::Add; use std::ops::Add;
use std::sync::Arc; use std::sync::Arc;
@ -49,13 +50,14 @@ pub async fn get_previous_message_or_reply(
Ok(referenced) Ok(referenced)
} }
/// Deletes all expired ephemeral messages that are stored in the database /// deletes all expired ephemeral messages that are stored in the database
pub async fn delete_messages_from_database(ctx: &Context) -> BotResult<()> { pub async fn delete_messages_from_database(ctx: &Context) -> BotResult<()> {
let database = get_database_from_context(ctx).await; let database = get_database_from_context(ctx).await;
let messages = database.get_ephemeral_messages().await?; let messages = database.get_ephemeral_messages().await?;
let now: DateTime<FixedOffset> = DateTime::<Local>::from(SystemTime::now()).into();
for message in messages { for message in messages {
if message.timeout <= SystemTime::now() { if message.timeout <= now {
tracing::debug!("Deleting message {:?}", message); tracing::debug!("Deleting message {:?}", message);
let _ = ctx let _ = ctx
.http .http
@ -73,9 +75,9 @@ pub async fn delete_messages_from_database(ctx: &Context) -> BotResult<()> {
); );
tokio::spawn(async move { tokio::spawn(async move {
tokio::time::sleep_until( tokio::time::sleep_until(Instant::now().add(std::time::Duration::from_millis(
Instant::now().add(message.timeout.duration_since(SystemTime::now()).unwrap()), (message.timeout - now).num_milliseconds() as u64,
) )))
.await; .await;
tracing::debug!("Deleting message {:?}", message); tracing::debug!("Deleting message {:?}", message);
let _ = http let _ = http

Loading…
Cancel
Save