diff --git a/migrations/2025-07-31-133510_roles_uuid_index/down.sql b/migrations/2025-07-31-133510_roles_uuid_index/down.sql new file mode 100644 index 0000000..efe3f3f --- /dev/null +++ b/migrations/2025-07-31-133510_roles_uuid_index/down.sql @@ -0,0 +1,5 @@ +-- This file should undo anything in `up.sql` +DROP INDEX roles_guuid_uuid; +ALTER TABLE roles DROP CONSTRAINT roles_pkey; +CREATE UNIQUE INDEX roles_pkey ON roles (uuid, guild_uuid); +ALTER TABLE roles ADD PRIMARY KEY USING INDEX roles_pkey; diff --git a/migrations/2025-07-31-133510_roles_uuid_index/up.sql b/migrations/2025-07-31-133510_roles_uuid_index/up.sql new file mode 100644 index 0000000..792e7fd --- /dev/null +++ b/migrations/2025-07-31-133510_roles_uuid_index/up.sql @@ -0,0 +1,5 @@ +-- Your SQL goes here +ALTER TABLE roles DROP CONSTRAINT roles_pkey; +CREATE UNIQUE INDEX roles_pkey ON roles (uuid); +ALTER TABLE roles ADD PRIMARY KEY USING INDEX roles_pkey; +CREATE UNIQUE INDEX roles_guuid_uuid ON roles (uuid, guild_uuid); \ No newline at end of file diff --git a/src/api/v1/members/uuid/ban.rs b/src/api/v1/members/uuid/ban.rs index b959efa..888dca6 100644 --- a/src/api/v1/members/uuid/ban.rs +++ b/src/api/v1/members/uuid/ban.rs @@ -33,7 +33,8 @@ pub async fn post( global_checks(&mut conn, &app_state.config, uuid).await?; - let member = Member::fetch_one_with_member(&mut conn, &app_state.cache_pool, None, member_uuid).await?; + let member = + Member::fetch_one_with_member(&mut conn, &app_state.cache_pool, None, member_uuid).await?; let caller = Member::check_membership(&mut conn, uuid, member.guild_uuid).await?; diff --git a/src/api/v1/members/uuid/mod.rs b/src/api/v1/members/uuid/mod.rs index 2bdd1ba..0832192 100644 --- a/src/api/v1/members/uuid/mod.rs +++ b/src/api/v1/members/uuid/mod.rs @@ -31,7 +31,9 @@ pub async fn get( let me = Me::get(&mut conn, uuid).await?; - let member = Member::fetch_one_with_member(&mut conn, &app_state.cache_pool, Some(&me), member_uuid).await?; + let member = + Member::fetch_one_with_member(&mut conn, &app_state.cache_pool, Some(&me), member_uuid) + .await?; Member::check_membership(&mut conn, uuid, member.guild_uuid).await?; Ok((StatusCode::OK, Json(member))) @@ -48,7 +50,9 @@ pub async fn delete( let me = Me::get(&mut conn, uuid).await?; - let member = Member::fetch_one_with_member(&mut conn, &app_state.cache_pool, Some(&me), member_uuid).await?; + let member = + Member::fetch_one_with_member(&mut conn, &app_state.cache_pool, Some(&me), member_uuid) + .await?; let deleter = Member::check_membership(&mut conn, uuid, member.guild_uuid).await?; diff --git a/src/objects/member.rs b/src/objects/member.rs index 3eb8c4d..a25bf66 100644 --- a/src/objects/member.rs +++ b/src/objects/member.rs @@ -1,6 +1,6 @@ use diesel::{ - ExpressionMethods, Insertable, QueryDsl, Queryable, Selectable, SelectableHelper, delete, - insert_into, + ExpressionMethods, Identifiable, Insertable, QueryDsl, Queryable, Selectable, SelectableHelper, + delete, insert_into, }; use diesel_async::RunQueryDsl; use serde::{Deserialize, Serialize}; @@ -15,8 +15,9 @@ use crate::{ use super::{User, load_or_empty}; -#[derive(Serialize, Queryable, Selectable, Insertable)] +#[derive(Serialize, Queryable, Identifiable, Selectable, Insertable)] #[diesel(table_name = guild_members)] +#[diesel(primary_key(uuid))] #[diesel(check_for_backend(diesel::pg::Pg))] pub struct MemberBuilder { pub uuid: Uuid, @@ -41,6 +42,8 @@ impl MemberBuilder { user = User::fetch_one(conn, cache_pool, self.user_uuid).await?; } + let roles = Role::fetch_from_member(conn, cache_pool, self).await?; + Ok(Member { uuid: self.uuid, nickname: self.nickname.clone(), @@ -48,6 +51,7 @@ impl MemberBuilder { guild_uuid: self.guild_uuid, is_owner: self.is_owner, user, + roles, }) } @@ -58,7 +62,7 @@ impl MemberBuilder { permission: Permissions, ) -> Result<(), Error> { if !self.is_owner { - let roles = Role::fetch_from_member(conn, cache_pool, self.uuid).await?; + let roles = Role::fetch_from_member(conn, cache_pool, self).await?; let allowed = roles.iter().any(|r| r.permissions & permission as i64 != 0); if !allowed { return Err(Error::Forbidden("Not allowed".to_string())); @@ -73,10 +77,12 @@ impl MemberBuilder { pub struct Member { pub uuid: Uuid, pub nickname: Option, + #[serde(skip)] pub user_uuid: Uuid, pub guild_uuid: Uuid, pub is_owner: bool, user: User, + roles: Vec, } impl Member { @@ -199,7 +205,7 @@ impl Member { pub async fn delete(self, conn: &mut Conn) -> Result<(), Error> { if self.is_owner { - return Err(Error::Forbidden("Can not kick owner".to_string())) + return Err(Error::Forbidden("Can not kick owner".to_string())); } delete(guild_members::table) .filter(guild_members::uuid.eq(self.uuid)) @@ -228,4 +234,14 @@ impl Member { Ok(()) } + + pub fn to_builder(&self) -> MemberBuilder { + MemberBuilder { + uuid: self.uuid, + nickname: self.nickname.clone(), + user_uuid: self.user_uuid, + guild_uuid: self.guild_uuid, + is_owner: self.is_owner, + } + } } diff --git a/src/objects/role.rs b/src/objects/role.rs index 46f54f6..cc71fb2 100644 --- a/src/objects/role.rs +++ b/src/objects/role.rs @@ -1,6 +1,7 @@ +use diesel::query_dsl::BelongingToDsl; use diesel::{ - ExpressionMethods, Insertable, QueryDsl, Queryable, Selectable, SelectableHelper, insert_into, - update, + Associations, ExpressionMethods, Identifiable, Insertable, QueryDsl, Queryable, Selectable, + SelectableHelper, insert_into, update, }; use diesel_async::RunQueryDsl; use serde::{Deserialize, Serialize}; @@ -13,10 +14,11 @@ use crate::{ utils::{CacheFns, order_by_is_above}, }; -use super::{HasIsAbove, HasUuid, load_or_empty}; +use super::{HasIsAbove, HasUuid, load_or_empty, member::MemberBuilder}; -#[derive(Deserialize, Serialize, Clone, Queryable, Selectable, Insertable)] +#[derive(Deserialize, Serialize, Clone, Identifiable, Queryable, Selectable, Insertable)] #[diesel(table_name = roles)] +#[diesel(primary_key(uuid))] #[diesel(check_for_backend(diesel::pg::Pg))] pub struct Role { uuid: Uuid, @@ -27,27 +29,17 @@ pub struct Role { pub permissions: i64, } -#[derive(Serialize, Clone, Queryable, Selectable, Insertable)] +#[derive(Serialize, Clone, Identifiable, Queryable, Selectable, Insertable, Associations)] #[diesel(table_name = role_members)] +#[diesel(belongs_to(MemberBuilder, foreign_key = member_uuid))] +#[diesel(belongs_to(Role, foreign_key = role_uuid))] +#[diesel(primary_key(role_uuid, member_uuid))] #[diesel(check_for_backend(diesel::pg::Pg))] pub struct RoleMember { role_uuid: Uuid, member_uuid: Uuid, } -impl RoleMember { - async fn fetch_role(&self, conn: &mut Conn) -> Result { - use roles::dsl; - let role: Role = dsl::roles - .filter(dsl::uuid.eq(self.role_uuid)) - .select(Role::as_select()) - .get_result(conn) - .await?; - - Ok(role) - } -} - impl HasUuid for Role { fn uuid(&self) -> &Uuid { self.uuid.as_ref() @@ -77,32 +69,25 @@ impl Role { pub async fn fetch_from_member( conn: &mut Conn, cache_pool: &redis::Client, - member_uuid: Uuid, + member: &MemberBuilder, ) -> Result, Error> { if let Ok(roles) = cache_pool - .get_cache_key(format!("{member_uuid}_roles")) + .get_cache_key(format!("{}_roles", member.uuid)) .await { return Ok(roles); } - use role_members::dsl; - let role_memberships: Vec = load_or_empty( - dsl::role_members - .filter(dsl::member_uuid.eq(member_uuid)) - .select(RoleMember::as_select()) + let roles: Vec = load_or_empty( + RoleMember::belonging_to(member) + .inner_join(roles::table) + .select(Role::as_select()) .load(conn) .await, )?; - let mut roles = vec![]; - - for membership in role_memberships { - roles.push(membership.fetch_role(conn).await?); - } - cache_pool - .set_cache_key(format!("{member_uuid}_roles"), roles.clone(), 300) + .set_cache_key(format!("{}_roles", member.uuid), roles.clone(), 300) .await?; Ok(roles) diff --git a/src/schema.rs b/src/schema.rs index 422c3a3..413f3f1 100644 --- a/src/schema.rs +++ b/src/schema.rs @@ -126,7 +126,7 @@ diesel::table! { } diesel::table! { - roles (uuid, guild_uuid) { + roles (uuid) { uuid -> Uuid, guild_uuid -> Uuid, #[max_length = 50] @@ -163,6 +163,7 @@ diesel::table! { diesel::joinable!(access_tokens -> refresh_tokens (refresh_token)); diesel::joinable!(access_tokens -> users (uuid)); diesel::joinable!(channel_permissions -> channels (channel_uuid)); +diesel::joinable!(channel_permissions -> roles (role_uuid)); diesel::joinable!(channels -> guilds (guild_uuid)); diesel::joinable!(guild_bans -> guilds (guild_uuid)); diesel::joinable!(guild_bans -> users (user_uuid)); @@ -175,6 +176,7 @@ diesel::joinable!(messages -> channels (channel_uuid)); diesel::joinable!(messages -> users (user_uuid)); diesel::joinable!(refresh_tokens -> users (uuid)); diesel::joinable!(role_members -> guild_members (member_uuid)); +diesel::joinable!(role_members -> roles (role_uuid)); diesel::joinable!(roles -> guilds (guild_uuid)); diesel::allow_tables_to_appear_in_same_query!(