diff options
| author | Nathan Perry <np@nathanperry.dev> | 2024-08-16 21:14:30 -0400 |
|---|---|---|
| committer | Nathan Perry <np@nathanperry.dev> | 2024-08-16 21:15:07 -0400 |
| commit | ef056edf92b678265a4666e1f9405e3b0ce66a42 (patch) | |
| tree | b3766d47ae2898d2a46f108de4e2be0ede6c400b /src/db | |
| parent | c5ce454319a7d54d3967c6ea7695543e943a37b2 (diff) | |
repo: overhaul for multitenancy
Diffstat (limited to 'src/db')
| -rw-r--r-- | src/db/manual_migrate.rs | 62 | ||||
| -rw-r--r-- | src/db/mod.rs | 338 | ||||
| -rw-r--r-- | src/db/models.rs | 19 | ||||
| -rw-r--r-- | src/db/schema.rs | 3 |
4 files changed, 294 insertions, 128 deletions
diff --git a/src/db/manual_migrate.rs b/src/db/manual_migrate.rs new file mode 100644 index 0000000..851f0d8 --- /dev/null +++ b/src/db/manual_migrate.rs @@ -0,0 +1,62 @@ +use crate::{ + db::{ + do_migrate, + schema::*, + POOL, + }, + guild_id, +}; +use anyhow::anyhow; +use diesel::{ + associations::HasTable, + backend::Backend, + pg::Pg, + query_builder::AsQuery, + ExpressionMethods, +}; +use diesel_async::{ + scoped_futures::ScopedFutureExt, + AsyncConnection, + AsyncPgConnection, + RunQueryDsl, +}; + +#[inline] +pub async fn connection_no_migrate() +-> anyhow::Result<diesel_async::pooled_connection::deadpool::Object<AsyncPgConnection>> { + let conn = super::POOL.get().await?; + Ok(conn) +} + +pub async fn set_default_guild<Conn>(mut conn: Conn, guild_id: u64) -> anyhow::Result<()> +where + Conn: AsyncConnection<Backend = Pg> + 'static, +{ + conn.transaction::<_, anyhow::Error, _>(|tx| { + (async move { + diesel::update(memes::table) + .filter(memes::guild.is_null()) + .set(memes::guild.eq(guild_id as i64)) + .execute(tx) + .await?; + + diesel::update(invocation_records::table) + .filter(invocation_records::guild.is_null()) + .set(invocation_records::guild.eq(guild_id as i64)) + .execute(tx) + .await?; + + diesel::update(tombstones::table) + .filter(tombstones::guild.is_null()) + .set(tombstones::guild.eq(guild_id as i64)) + .execute(tx) + .await?; + + Ok(()) + }) + .scope_boxed() + }) + .await?; + + Ok(()) +} diff --git a/src/db/mod.rs b/src/db/mod.rs index e8e6596..5f19d54 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -1,9 +1,6 @@ -use std::{ - convert::AsRef, - env, - str::FromStr, -}; - +pub use self::models::*; +use self::schema::*; +use crate::db::schema::memes::title; use anyhow::{ anyhow, Error, @@ -22,7 +19,12 @@ use deadpool_postgres::{ }; use diesel::{ prelude::*, + row::NamedRow, + BoolExpressionMethods, + ExpressionMethods, NotFound, + PgTextExpressionMethods, + QueryDsl, }; use diesel_async::{ pooled_connection::{ @@ -35,11 +37,19 @@ use diesel_async::{ RunQueryDsl, }; use grate::tracing; +use rand::Rng; use serenity::FutureExt; +use std::{ + convert::{ + AsRef, + From, + }, + env, + str::FromStr, +}; +use tokio_postgres::types::FromSql; -pub use self::models::*; -use self::schema::*; - +pub mod manual_migrate; mod models; mod schema; @@ -72,7 +82,7 @@ lazy_static::lazy_static! { #[inline] pub async fn connection() --> Result<diesel_async::pooled_connection::deadpool::Object<AsyncPgConnection>> { + -> Result<diesel_async::pooled_connection::deadpool::Object<AsyncPgConnection>> { POOL.get() .then(|mut conn| async move { if let Ok(ref mut conn) = conn { @@ -105,16 +115,30 @@ async fn raw_connection() -> Result<deadpool_postgres::Object> { RAW_CONN_MGR.get().await.map_err(Error::from) } -pub async fn find_meme<T: AsRef<str>>(conn: &mut AsyncPgConnection, search: T) -> Result<Meme> { +pub async fn find_meme<T: AsRef<str>>( + conn: &mut AsyncPgConnection, + search: T, + guild_id: u64, +) -> Result<Meme> { let search = search.as_ref(); + let guild_match = memes::guild.eq(guild_id as i64); - let mut meme = memes::table.filter(memes::title.eq(search)).limit(1).first::<Meme>(conn).await; + let mut meme = memes::table + .filter(memes::title.eq(search).and(guild_match)) + .limit(1) + .first::<Meme>(conn) + .await; if let Err(NotFound) = meme { let format_search = format!("%{}%", search); meme = memes::table - .filter(memes::title.ilike(&format_search).or(memes::content.ilike(&format_search))) + .filter( + memes::title + .ilike(&format_search) + .or(memes::content.ilike(&format_search)) + .and(guild_match), + ) .limit(1) .first::<Meme>(conn) .await; @@ -127,9 +151,11 @@ pub async fn query_meme<T: AsRef<str>>( search: T, user_id: Option<u64>, age_desc: bool, + guild: u64, ) -> Result<Vec<(Meme, Metadata)>> { let raw_conn = raw_connection().await?; + let guild = guild as i64; let search = format!("%{}%", search.as_ref()); let rows = raw_conn @@ -140,7 +166,7 @@ pub async fn query_meme<T: AsRef<str>>( FROM memes INNER JOIN metadata ON memes.metadata_id = metadata.id WHERE (memes.title ILIKE $1 OR memes.content ILIKE $1) - AND (metadata.created_by = $2 OR $3) + AND (metadata.created_by = $2 OR $3) AND memes.guild = $4 ORDER BY metadata.created {} LIMIT 100 "#, @@ -150,7 +176,7 @@ pub async fn query_meme<T: AsRef<str>>( "ASC" }, ), - &[&search, &(user_id.unwrap_or(0) as i64), &user_id.is_none()], + &[&search, &(user_id.unwrap_or(0) as i64), &user_id.is_none(), &guild], ) .await?; @@ -158,17 +184,18 @@ pub async fn query_meme<T: AsRef<str>>( .iter() .map(|row| { let meme = Meme { - id: row.get(0), - title: row.get(1), - content: row.get(2), - image_id: row.get(3), - audio_id: row.get(4), + id: row.get(0), + guild, + title: row.get(1), + content: row.get(2), + image_id: row.get(3), + audio_id: row.get(4), metadata_id: row.get(5), }; let metadata = Metadata { - id: row.get(5), - created: row.get(6), + id: row.get(5), + created: row.get(6), created_by: row.get(7), }; @@ -196,7 +223,7 @@ pub async fn del_memes_by_userid( meme_deletes AS ( DELETE FROM memes WHERE metadata_id IN (SELECT id FROM metadata WHERE created_by = $1) - RETURNING id, metadata_id + RETURNING id, guild, metadata_id ) , image_deletes AS ( DELETE FROM images @@ -209,8 +236,8 @@ pub async fn del_memes_by_userid( RETURNING id, metadata_id ) , tombstone_inserts AS ( - INSERT INTO tombstones (meme_id, metadata_id, deleted_at, deleted_by) - SELECT id, metadata_id, NOW(), $2 FROM meme_deletes + INSERT INTO tombstones (meme_id, guild, metadata_id, deleted_at, deleted_by) + SELECT id, guild, metadata_id, NOW(), $2 FROM meme_deletes RETURNING meme_id, metadata_id ) @@ -229,12 +256,16 @@ pub async fn delete_meme<T: AsRef<str>>( conn: &mut AsyncPgConnection, search: T, deleted_by: u64, + guild: u64, ) -> Result<()> { let search = search.as_ref(); conn.transaction::<(), Error, _>(|tx| { async move { - let deleted = memes::table.filter(memes::title.eq(search)).first::<Meme>(tx).await?; + let deleted = memes::table + .filter(memes::title.eq(search).and(memes::guild.eq(guild as i64))) + .first::<Meme>(tx) + .await?; diesel::delete(memes::table).filter(memes::id.eq(deleted.id)).execute(tx).await?; @@ -263,23 +294,29 @@ pub async fn delete_meme<T: AsRef<str>>( } let tombstone = NewTombstone { - deleted_by: deleted_by as i64, + guild: guild as i64, + deleted_by: deleted_by as i64, metadata_id: deleted.metadata_id, - meme_id: deleted.id, + meme_id: deleted.id, }; let _ = diesel::insert_into(tombstones::table).values(&tombstone).execute(tx).await?; Ok(()) } - .scope_boxed() + .scope_boxed() }) - .await + .await } -pub async fn rare_meme(conn: &mut AsyncPgConnection, audio: bool) -> Result<Meme> { +pub async fn rare_meme( + conn: &mut AsyncPgConnection, + audio: bool, + guild: u64, +) -> Result<Option<Meme>> { use rand::prelude::*; + let guild = guild as i64; let raw_conn = raw_connection().await?; let rows = raw_conn @@ -291,6 +328,7 @@ pub async fn rare_meme(conn: &mut AsyncPgConnection, audio: bool) -> Result<Meme meme_id, COUNT(*) AS ct FROM invocation_records + WHERE guild = $3 GROUP BY meme_id ), aggregate AS ( @@ -301,7 +339,7 @@ pub async fn rare_meme(conn: &mut AsyncPgConnection, audio: bool) -> Result<Meme FROM meme_count RIGHT JOIN memes ON memes.id = meme_count.meme_id INNER JOIN metadata ON metadata.id = memes.metadata_id - WHERE (memes.audio_id IS NULL) = $1 OR $2 + WHERE memes.guild = $3 AND ((memes.audio_id IS NULL) = $1 OR $2) ), least_used AS ( SELECT @@ -315,7 +353,7 @@ pub async fn rare_meme(conn: &mut AsyncPgConnection, audio: bool) -> Result<Meme FROM least_used LIMIT 100; "#, - &[&!audio, &audio], + &[&!audio, &audio, &guild], ) .await?; @@ -325,7 +363,7 @@ pub async fn rare_meme(conn: &mut AsyncPgConnection, audio: bool) -> Result<Meme .collect::<Vec<_>>(); if elems.is_empty() { - return Err(anyhow!("no rare memes found")); + return Ok(None); } let target_prob = { @@ -339,10 +377,14 @@ pub async fn rare_meme(conn: &mut AsyncPgConnection, audio: bool) -> Result<Meme .ok_or_else(|| anyhow!("couldn't locate meme satisfying target probability"))? .0; - Meme::find(conn, meme_id).await + Meme::find(conn, meme_id).await.map(Some) } -pub async fn rand_meme(conn: &mut AsyncPgConnection, audio: bool) -> Result<Meme> { +pub async fn rand_meme( + conn: &mut AsyncPgConnection, + audio: bool, + guild: u64, +) -> Result<Option<Meme>> { use rand::{ seq::SliceRandom, thread_rng, @@ -352,10 +394,12 @@ pub async fn rand_meme(conn: &mut AsyncPgConnection, audio: bool) -> Result<Meme memes::table .select(memes::id) .filter( - memes::content - .is_not_null() - .or(memes::image_id.is_not_null()) - .or(memes::audio_id.is_not_null()), + memes::guild.eq(guild as i64).and( + memes::content + .is_not_null() + .or(memes::image_id.is_not_null()) + .or(memes::audio_id.is_not_null()), + ), ) .load(conn) .await @@ -363,18 +407,26 @@ pub async fn rand_meme(conn: &mut AsyncPgConnection, audio: bool) -> Result<Meme } else { memes::table .select(memes::id) - .filter(memes::content.is_not_null().or(memes::image_id.is_not_null())) + .filter( + memes::guild + .eq(guild as i64) + .and(memes::content.is_not_null().or(memes::image_id.is_not_null())), + ) .load(conn) .await .map_err(Error::from)? }; + if ids.is_empty() { + return Ok(None); + } + let id = ids.choose(&mut thread_rng()).ok_or_else(|| anyhow!("couldn't load meme"))?; - memes::table.find(id).first::<Meme>(conn).await.map_err(Error::from) + memes::table.find(id).first::<Meme>(conn).await.map_err(Error::from).map(Some) } -pub async fn rand_audio_meme(conn: &mut AsyncPgConnection) -> Result<Meme> { +pub async fn rand_audio_meme(conn: &mut AsyncPgConnection, guild: u64) -> Result<Option<Meme>> { use rand::{ seq::SliceRandom, thread_rng, @@ -382,17 +434,21 @@ pub async fn rand_audio_meme(conn: &mut AsyncPgConnection) -> Result<Meme> { let ids: Vec<i32> = memes::table .select(memes::id) - .filter(memes::audio_id.is_not_null()) + .filter(memes::audio_id.is_not_null().and(memes::guild.eq(guild as i64))) .load(conn) .await .map_err(Error::from)?; + if ids.is_empty() { + return Ok(None); + } + let id = ids.choose(&mut thread_rng()).ok_or_else(|| anyhow!("couldn't load audio meme"))?; - memes::table.find(id).first::<Meme>(conn).await.map_err(Error::from) + memes::table.find(id).first::<Meme>(conn).await.map_err(Error::from).map(Some) } -pub async fn rand_silent_meme(conn: &mut AsyncPgConnection) -> Result<Meme> { +pub async fn rand_silent_meme(conn: &mut AsyncPgConnection, guild: u64) -> Result<Option<Meme>> { use rand::{ seq::SliceRandom, thread_rng, @@ -400,218 +456,246 @@ pub async fn rand_silent_meme(conn: &mut AsyncPgConnection) -> Result<Meme> { let ids: Vec<i32> = memes::table .select(memes::id) - .filter(memes::audio_id.is_null()) + .filter(memes::audio_id.is_null().and(memes::guild.eq(guild as i64))) .load(conn) .await .map_err(Error::from)?; + if ids.is_empty() { + return Ok(None); + } + let id = ids.choose(&mut thread_rng()).ok_or_else(|| anyhow!("couldn't load audio meme"))?; - memes::table.find(id).first::<Meme>(conn).await.map_err(Error::from) + memes::table.find(id).first::<Meme>(conn).await.map_err(Error::from).map(Some) } #[derive(Debug, Clone)] pub struct Stats { - pub memes_overall: usize, - pub audio_memes: usize, - pub image_memes: usize, - pub started_recording: DateTime<Utc>, - pub total_meme_invocations: usize, - pub audio_meme_invocations: usize, + pub memes_overall: usize, + pub audio_memes: usize, + pub image_memes: usize, + pub started_recording: Option<DateTime<Utc>>, + pub total_meme_invocations: usize, + pub audio_meme_invocations: usize, pub random_meme_invocations: usize, - pub most_active_day: NaiveDate, + pub most_active_day: Option<NaiveDate>, pub most_active_day_count: usize, - pub most_audio_active_day: NaiveDate, + pub most_audio_active_day: Option<NaiveDate>, pub most_audio_active_count: usize, - pub most_random_meme_user: u64, - pub most_random_meme_user_count: usize, - pub most_directly_named_meme_user: u64, + pub most_random_meme_user: Option<u64>, + pub most_random_meme_user_count: usize, + pub most_directly_named_meme_user: Option<u64>, pub most_directly_named_meme_count: usize, - pub most_popular_named_meme: String, + pub most_popular_named_meme: Option<String>, pub most_popular_named_meme_count: usize, - pub most_popular_random_meme: String, + pub most_popular_random_meme: Option<String>, pub most_popular_random_meme_count: usize, - pub most_popular_meme_overall: String, + pub most_popular_meme_overall: Option<String>, pub most_popular_meme_overall_count: usize, } -pub async fn stats(conn: &mut AsyncPgConnection) -> Result<Stats> { +pub async fn stats(conn: &mut AsyncPgConnection, guild: u64) -> Result<Stats> { use chrono::NaiveDateTime; use diesel::dsl::{ count, count_star, }; + let guild = guild as i64; + + let guild_match = memes::guild.eq(guild); + let invoke_guild_match = invocation_records::guild.eq(guild); + #[inline] fn to_utc(ndt: NaiveDateTime) -> DateTime<Utc> { Utc.from_utc_datetime(&ndt) } - let total_count: i64 = - memes::table.select(count_star()).first(conn).await.map_err(Error::from)?; + let total_count: i64 = memes::table + .select(count_star()) + .filter(guild_match) + .first(conn) + .await + .map_err(Error::from)?; let image_count: i64 = memes::table .select(count(memes::image_id)) - .filter(memes::image_id.is_not_null()) + .filter(memes::image_id.is_not_null().and(guild_match)) .first(conn) .await .map_err(Error::from)?; let audio_count: i64 = memes::table .select(count(memes::audio_id)) - .filter(memes::audio_id.is_not_null()) + .filter(memes::audio_id.is_not_null().and(guild_match)) .first(conn) .await .map_err(Error::from)?; - let started_recording: NaiveDateTime = invocation_records::table + let started_recording = invocation_records::table .select(invocation_records::time) + .filter(invoke_guild_match) .order(invocation_records::time) .first(conn) .await - .map_err(Error::from)?; + .optional(); + + tracing::debug!(?started_recording); - let started_recording = to_utc(started_recording); + let started_recording: Option<NaiveDateTime> = started_recording.map_err(Error::from)?; + let started_recording = started_recording.map(to_utc); - let total_meme_invocations: i64 = - invocation_records::table.select(count_star()).first(conn).await.map_err(Error::from)?; + let total_meme_invocations: i64 = invocation_records::table + .select(count_star()) + .filter(invoke_guild_match) + .first(conn) + .await + .map_err(Error::from)?; let audio_meme_invocations: i64 = invocation_records::table .inner_join(memes::table) .select(count_star()) - .filter(memes::audio_id.is_not_null()) + .filter(memes::audio_id.is_not_null().and(guild_match)) .first(conn) .await .map_err(Error::from)?; let random_meme_invocations: i64 = invocation_records::table .select(count_star()) - .filter(invocation_records::random.eq(true)) + .filter(invocation_records::random.eq(true).and(invoke_guild_match)) .first(conn) .await .map_err(Error::from)?; let raw_conn = raw_connection().await?; + #[inline] + fn option_first_count<T>(rows: Vec<tokio_postgres::Row>) -> (Option<T>, i64) + where + for<'a> T: FromSql<'a>, + { + let Some(row) = <[_]>::first(&rows) else { + return (None, 0); + }; + + (row.get(0), row.get(1)) + } + let row = raw_conn - .query_one( + .query( r#" SELECT DATE(time) as dt, COUNT(*) FROM invocation_records + WHERE guild = $1 GROUP BY dt ORDER BY COUNT(*) DESC LIMIT 1; "#, - &[], + &[&guild], ) .await?; - let most_active_day = row.get(0); - let most_active_day_count: i64 = row.get(1); + let (most_active_day, most_active_day_count) = option_first_count(row); let row = raw_conn - .query_one( + .query( r#" SELECT DATE(time) as dt, COUNT(*) FROM invocation_records - INNER JOIN memes ON invocation_records.meme_id = memes.id - WHERE memes.audio_id IS NOT NULL + INNER JOIN memes ON (invocation_records.meme_id = memes.id AND invocation_records.guild = memes.guild) + WHERE memes.audio_id IS NOT NULL AND memes.guild = $1 GROUP BY dt ORDER BY COUNT(*) DESC LIMIT 1; "#, - &[], + &[&guild], ) .await?; - let most_active_audio_day = row.get(0); - let most_active_audio_day_count: i64 = row.get(1); + let (most_active_audio_day, most_active_audio_day_count) = option_first_count(row); let row = raw_conn - .query_one( + .query( r#" SELECT user_id, COUNT(*) FROM invocation_records - WHERE random IS TRUE + WHERE random IS TRUE AND guild = $1 GROUP BY user_id ORDER BY COUNT(*) DESC LIMIT 1; "#, - &[], + &[&guild], ) .await?; - let most_random_invoker: i64 = row.get(0); - let most_random_invoker_count: i64 = row.get(1); + let (most_random_invoker, most_random_invoker_count) = option_first_count::<i64>(row); let row = raw_conn - .query_one( + .query( r#" SELECT user_id, COUNT(*) FROM invocation_records - WHERE random IS FALSE + WHERE random IS FALSE AND guild = $1 GROUP BY user_id ORDER BY COUNT(*) DESC LIMIT 1; "#, - &[], + &[&guild], ) .await?; - let most_specific_invoker: i64 = row.get(0); - let most_specific_invoker_count: i64 = row.get(1); + let (most_specific_invoker, most_specific_invoker_count) = option_first_count::<i64>(row); let row = raw_conn - .query_one( + .query( r#" SELECT memes.title, COUNT(*) FROM invocation_records - INNER JOIN memes ON meme_id = memes.id - WHERE random IS FALSE + INNER JOIN memes ON (meme_id = memes.id AND invocation_records.guild = memes.guild) + WHERE random IS FALSE AND memes.guild = $1 GROUP BY memes.title ORDER BY COUNT(*) DESC LIMIT 1; "#, - &[], + &[&guild], ) .await?; - let most_requested_meme = row.get(0); - let most_requested_meme_count: i64 = row.get(1); + let (most_requested_meme, most_requested_meme_count) = option_first_count(row); let row = raw_conn - .query_one( + .query( r#" SELECT memes.title, COUNT(*) FROM invocation_records - INNER JOIN memes ON meme_id = memes.id - WHERE random IS TRUE + INNER JOIN memes ON (meme_id = memes.id AND invocation_records.guild = memes.guild) + WHERE random IS TRUE AND memes.guild = $1 GROUP BY memes.title ORDER BY COUNT(*) DESC LIMIT 1; "#, - &[], + &[&guild], ) .await?; - let most_random_meme = row.get(0); - let most_random_meme_count: i64 = row.get(1); + let (most_random_meme, most_random_meme_count) = option_first_count(row); let row = raw_conn - .query_one( + .query( r#" SELECT memes.title, COUNT(*) FROM invocation_records - INNER JOIN memes ON meme_id = memes.id + INNER JOIN memes ON (meme_id = memes.id AND invocation_records.guild = memes.guild) + WHERE memes.guild = $1 GROUP BY memes.title ORDER BY COUNT(*) DESC LIMIT 1; "#, - &[], + &[&guild], ) .await?; - let most_invoked_meme = row.get(0); - let most_invoked_meme_count: i64 = row.get(1); + let (most_invoked_meme, most_invoked_meme_count) = option_first_count(row); Ok(Stats { memes_overall: total_count as usize, @@ -624,12 +708,13 @@ pub async fn stats(conn: &mut AsyncPgConnection) -> Result<Stats> { most_active_day, most_active_day_count: most_active_day_count as usize, + most_audio_active_day: most_active_audio_day, most_audio_active_count: most_active_audio_day_count as usize, - most_random_meme_user: most_random_invoker as u64, + most_random_meme_user: most_random_invoker.map(|x| x as u64), most_random_meme_user_count: most_random_invoker_count as usize, - most_directly_named_meme_user: most_specific_invoker as u64, + most_directly_named_meme_user: most_specific_invoker.map(|x| x as u64), most_directly_named_meme_count: most_specific_invoker_count as usize, most_popular_named_meme: most_requested_meme, @@ -645,33 +730,34 @@ pub async fn stats(conn: &mut AsyncPgConnection) -> Result<Stats> { #[derive(Clone, Debug, Hash, PartialEq, Eq, Default)] pub struct MemerInfo { - pub user_id: u64, - pub random_memes: usize, - pub specific_memes: usize, - pub most_used_meme: String, + pub user_id: u64, + pub random_memes: usize, + pub specific_memes: usize, + pub most_used_meme: String, pub most_used_meme_count: usize, } -pub async fn memers() -> Result<Vec<MemerInfo>> { +pub async fn memers(guild: u64) -> Result<Vec<MemerInfo>> { + let guild = guild as i64; let raw_conn = raw_connection().await?; let rows = raw_conn.query(r#" WITH random_count AS ( SELECT user_id, COUNT(*) as count FROM invocation_records - WHERE random = TRUE + WHERE random = TRUE AND guild = $1 GROUP BY user_id ), specific_count AS ( SELECT user_id, COUNT(*) as count FROM invocation_records - WHERE random = FALSE + WHERE random = FALSE AND guild = $1 GROUP BY user_id ), user_meme_counts AS ( SELECT user_id, meme_id, COUNT(meme_id) as meme_count FROM invocation_records - WHERE EXISTS (SELECT id FROM memes WHERE id = invocation_records.meme_id) + WHERE EXISTS (SELECT id FROM memes WHERE id = invocation_records.meme_id) AND guild = $1 GROUP BY user_id, meme_id ORDER BY user_id, meme_count DESC ), @@ -692,7 +778,7 @@ pub async fn memers() -> Result<Vec<MemerInfo>> { INNER JOIN specific_count ON specific_count.user_id = random_count.user_id INNER JOIN memes ON memes.id = most_memed.meme_id ORDER BY (random_count.count + specific_count.count) DESC - "#, &[]).await?; + "#, &[&guild]).await?; let result = rows .iter() @@ -704,10 +790,10 @@ pub async fn memers() -> Result<Vec<MemerInfo>> { let most_memed_count: i64 = row.get(4); MemerInfo { - user_id: user_id as u64, - random_memes: random_count as usize, - specific_memes: specific_count as usize, - most_used_meme: most_memed_meme, + user_id: user_id as u64, + random_memes: random_count as usize, + specific_memes: specific_count as usize, + most_used_meme: most_memed_meme, most_used_meme_count: most_memed_count as usize, } }) diff --git a/src/db/models.rs b/src/db/models.rs index 603887e..33fa34e 100644 --- a/src/db/models.rs +++ b/src/db/models.rs @@ -4,6 +4,7 @@ use anyhow::{ }; use chrono::naive::NaiveDateTime; use diesel::{ + associations::HasTable, prelude::*, Identifiable, Insertable, @@ -21,6 +22,7 @@ use crate::db::schema::*; #[diesel(table_name = memes)] pub struct Meme { pub id: i32, + pub guild: i64, pub title: String, pub content: Option<String>, pub image_id: Option<i32>, @@ -55,6 +57,7 @@ impl Meme { #[diesel(table_name = memes)] pub struct NewMeme { pub title: String, + pub guild: i64, pub content: Option<String>, pub image_id: Option<i32>, pub audio_id: Option<i32>, @@ -250,6 +253,7 @@ pub struct NewAuditRecord { #[diesel(table_name = tombstones)] pub struct Tombstone { pub id: i32, + pub guild: i64, pub deleted: NaiveDateTime, pub deleted_by: i64, pub metadata_id: i32, @@ -260,6 +264,7 @@ pub struct Tombstone { #[diesel(table_name = tombstones)] pub struct NewTombstone { pub deleted_by: i64, + pub guild: i64, pub metadata_id: i32, pub meme_id: i32, } @@ -269,6 +274,7 @@ pub struct NewTombstone { pub struct InvocationRecord { pub id: i32, pub user_id: i64, + pub guild: i64, pub message_id: i64, pub meme_id: i32, pub time: NaiveDateTime, @@ -279,6 +285,7 @@ pub struct InvocationRecord { #[diesel(table_name = invocation_records)] pub struct NewInvocationRecord { pub user_id: i64, + pub guild: i64, pub message_id: i64, pub meme_id: i32, pub random: bool, @@ -288,6 +295,7 @@ impl InvocationRecord { pub async fn create( conn: &mut AsyncPgConnection, user_id: u64, + guild_id: u64, message_id: u64, meme_id: i32, random: bool, @@ -296,6 +304,7 @@ impl InvocationRecord { .values(&NewInvocationRecord { user_id: user_id as i64, message_id: message_id as i64, + guild: guild_id as i64, meme_id, random, }) @@ -304,16 +313,22 @@ impl InvocationRecord { .map_err(Error::from) } - pub async fn last(conn: &mut AsyncPgConnection) -> Result<Self> { + pub async fn last(conn: &mut AsyncPgConnection, guild_id: u64) -> Result<Self> { invocation_records::table + .filter(invocation_records::guild.eq(guild_id as i64)) .order(invocation_records::time.desc()) .first(conn) .await .map_err(Error::from) } - pub async fn last_n(conn: &mut AsyncPgConnection, n: usize) -> Result<Vec<Self>> { + pub async fn last_n( + conn: &mut AsyncPgConnection, + n: usize, + guild_id: u64, + ) -> Result<Vec<Self>> { invocation_records::table + .filter(invocation_records::guild.eq(guild_id as i64)) .order(invocation_records::time.desc()) .limit(n as i64) .load(conn) diff --git a/src/db/schema.rs b/src/db/schema.rs index e89b24a..bc17e29 100644 --- a/src/db/schema.rs +++ b/src/db/schema.rs @@ -30,6 +30,7 @@ diesel::table! { invocation_records (id) { id -> Int4, user_id -> Int8, + guild -> Int8, message_id -> Int8, meme_id -> Int4, time -> Timestamp, @@ -40,6 +41,7 @@ diesel::table! { diesel::table! { memes (id) { id -> Int4, + guild -> Int8, title -> Varchar, content -> Nullable<Text>, image_id -> Nullable<Int4>, @@ -60,6 +62,7 @@ diesel::table! { tombstones (id) { id -> Int4, meme_id -> Int4, + guild -> Int8, deleted_by -> Int8, deleted_at -> Timestamp, metadata_id -> Nullable<Int4>, |
