use diesel::{delete, insert_into, prelude::{Insertable, Queryable}, ExpressionMethods, QueryDsl, Selectable, SelectableHelper}; use serde::{Deserialize, Serialize}; use uuid::Uuid; use diesel_async::{pooled_connection::AsyncDieselConnectionManager, RunQueryDsl}; use crate::{error::Error, Conn, Data, schema::*}; fn load_or_empty(query_result: Result, diesel::result::Error>) -> Result, diesel::result::Error> { match query_result { Ok(vec) => Ok(vec), Err(diesel::result::Error::NotFound) => Ok(Vec::new()), Err(e) => Err(e), } } #[derive(Queryable, Selectable, Insertable, Clone)] #[diesel(table_name = channels)] #[diesel(check_for_backend(diesel::pg::Pg))] struct ChannelBuilder { uuid: Uuid, guild_uuid: Uuid, name: String, description: Option, } impl ChannelBuilder { async fn build(self, conn: &mut Conn) -> Result { use self::channel_permissions::dsl::*; let channel_permission: Vec = load_or_empty( channel_permissions .filter(channel_uuid.eq(self.uuid)) .select(ChannelPermission::as_select()) .load(conn) .await )?; Ok(Channel { uuid: self.uuid, guild_uuid: self.guild_uuid, name: self.name, description: self.description, permissions: channel_permission, }) } } #[derive(Serialize, Deserialize, Clone)] pub struct Channel { pub uuid: Uuid, pub guild_uuid: Uuid, name: String, description: Option, pub permissions: Vec, } #[derive(Serialize, Deserialize, Clone, Queryable, Selectable)] #[diesel(table_name = channel_permissions)] #[diesel(check_for_backend(diesel::pg::Pg))] pub struct ChannelPermission { pub role_uuid: Uuid, pub permissions: i64, } impl Channel { pub async fn fetch_all( pool: &deadpool::managed::Pool, Conn>, guild_uuid: Uuid, ) -> Result, Error> { let mut conn = pool.get().await?; use channels::dsl; let channel_builders: Vec = load_or_empty( dsl::channels .filter(dsl::guild_uuid.eq(guild_uuid)) .select(ChannelBuilder::as_select()) .load(&mut conn) .await )?; let channel_futures = channel_builders.iter().map(async move |c| { let mut conn = pool.get().await?; c.clone().build(&mut conn).await }); futures::future::try_join_all(channel_futures).await } pub async fn fetch_one( conn: &mut Conn, channel_uuid: Uuid, ) -> Result { use channels::dsl; let channel_builder: ChannelBuilder = dsl::channels .filter(dsl::uuid.eq(channel_uuid)) .select(ChannelBuilder::as_select()) .get_result(conn) .await?; channel_builder.build(conn).await } pub async fn new( data: actix_web::web::Data, guild_uuid: Uuid, name: String, description: Option, ) -> Result { let mut conn = data.pool.get().await?; let channel_uuid = Uuid::now_v7(); let new_channel = ChannelBuilder { uuid: channel_uuid, guild_uuid: guild_uuid, name: name.clone(), description: description.clone(), }; insert_into(channels::table) .values(new_channel) .execute(&mut conn) .await?; // returns different object because there's no reason to build the channelbuilder (wastes 1 database request) let channel = Self { uuid: channel_uuid, guild_uuid, name, description, permissions: vec![], }; data .set_cache_key(channel_uuid.to_string(), channel.clone(), 1800) .await?; data.del_cache_key(format!("{}_channels", guild_uuid)).await?; Ok(channel) } pub async fn delete(self, conn: &mut Conn) -> Result<(), Error> { use channels::dsl; delete(channels::table) .filter(dsl::uuid.eq(self.uuid)) .execute(conn) .await?; Ok(()) } pub async fn fetch_messages( &self, conn: &mut Conn, amount: i64, offset: i64, ) -> Result, Error> { use messages::dsl; let messages: Vec = load_or_empty( dsl::messages .filter(dsl::channel_uuid.eq(self.uuid)) .select(Message::as_select()) .limit(amount) .offset(offset) .load(conn) .await )?; Ok(messages) } pub async fn new_message( &self, conn: &mut Conn, user_uuid: Uuid, message: String, ) -> Result { let message_uuid = Uuid::now_v7(); let message = Message { uuid: message_uuid, channel_uuid: self.uuid, user_uuid, message, }; insert_into(messages::table) .values(message.clone()) .execute(conn) .await?; Ok(message) } } #[derive(Clone, Copy)] pub enum Permissions { SendMessage = 1, CreateChannel = 2, DeleteChannel = 4, ManageChannel = 8, CreateRole = 16, DeleteRole = 32, ManageRole = 64, CreateInvite = 128, ManageInvite = 256, ManageServer = 512, ManageMember = 1024, } impl Permissions { pub fn fetch_permissions(permissions: i64) -> Vec { let all_perms = vec![ Self::SendMessage, Self::CreateChannel, Self::DeleteChannel, Self::ManageChannel, Self::CreateRole, Self::DeleteRole, Self::ManageRole, Self::CreateInvite, Self::ManageInvite, Self::ManageServer, Self::ManageMember, ]; all_perms .into_iter() .filter(|p| permissions & (*p as i64) != 0) .collect() } } #[derive(Serialize, Queryable, Selectable, Insertable, Clone)] #[diesel(table_name = guilds)] #[diesel(check_for_backend(diesel::pg::Pg))] struct GuildBuilder { uuid: Uuid, name: String, description: Option, owner_uuid: Uuid, } impl GuildBuilder { async fn build(self, conn: &mut Conn) -> Result { let member_count = Member::count(conn, self.uuid).await?; let roles = Role::fetch_all(conn, self.uuid).await?; Ok(Guild { uuid: self.uuid, name: self.name, description: self.description, icon: String::from("bogus"), owner_uuid: self.owner_uuid, roles: roles, member_count: member_count, }) } } #[derive(Serialize)] pub struct Guild { pub uuid: Uuid, name: String, description: Option, icon: String, owner_uuid: Uuid, pub roles: Vec, member_count: i64, } impl Guild { pub async fn fetch_one(conn: &mut Conn, guild_uuid: Uuid) -> Result { use guilds::dsl; let guild_builder: GuildBuilder = dsl::guilds .filter(dsl::uuid.eq(guild_uuid)) .select(GuildBuilder::as_select()) .get_result(conn) .await?; guild_builder.build(conn).await } pub async fn fetch_amount( pool: &deadpool::managed::Pool, Conn>, offset: i64, amount: i64, ) -> Result, Error> { // Fetch guild data from database let mut conn = pool.get().await?; use guilds::dsl; let guild_builders: Vec = load_or_empty( dsl::guilds .select(GuildBuilder::as_select()) .order_by(dsl::uuid) .offset(offset) .limit(amount) .load(&mut conn) .await )?; // Process each guild concurrently let guild_futures = guild_builders.iter().map(async move |g| { let mut conn = pool.get().await?; g.clone().build(&mut conn).await }); // Execute all futures concurrently and collect results futures::future::try_join_all(guild_futures).await } pub async fn new( conn: &mut Conn, name: String, description: Option, owner_uuid: Uuid, ) -> Result { let guild_uuid = Uuid::now_v7(); let guild_builder = GuildBuilder { uuid: guild_uuid, name: name.clone(), description: description.clone(), owner_uuid, }; insert_into(guilds::table) .values(guild_builder) .execute(conn) .await?; let member_uuid = Uuid::now_v7(); let member = Member { uuid: member_uuid, nickname: None, user_uuid: owner_uuid, guild_uuid, }; insert_into(guild_members::table) .values(member) .execute(conn) .await?; Ok(Guild { uuid: guild_uuid, name, description, icon: "bogus".to_string(), owner_uuid, roles: vec![], member_count: 1, }) } pub async fn get_invites(&self, conn: &mut Conn) -> Result, Error> { use invites::dsl; let invites = load_or_empty( dsl::invites .filter(dsl::guild_uuid.eq(self.uuid)) .select(Invite::as_select()) .load(conn) .await )?; Ok(invites) } pub async fn create_invite( &self, conn: &mut Conn, member: &Member, custom_id: Option, ) -> Result { let invite_id; if let Some(id) = custom_id { invite_id = id; if invite_id.len() > 32 { return Err(Error::BadRequest("MAX LENGTH".to_string())) } } else { let charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; invite_id = random_string::generate(8, charset); } let invite = Invite { id: invite_id, user_uuid: member.user_uuid, guild_uuid: self.uuid, }; insert_into(invites::table) .values(invite.clone()) .execute(conn) .await?; Ok(invite) } } #[derive(Serialize, Clone, Queryable, Selectable, Insertable)] #[diesel(table_name = roles)] #[diesel(check_for_backend(diesel::pg::Pg))] pub struct Role { uuid: Uuid, guild_uuid: Uuid, name: String, color: i32, position: i32, permissions: i64, } impl Role { pub async fn fetch_all( conn: &mut Conn, guild_uuid: Uuid, ) -> Result, Error> { use roles::dsl; let roles: Vec = load_or_empty( dsl::roles .filter(dsl::guild_uuid.eq(guild_uuid)) .select(Role::as_select()) .load(conn) .await )?; Ok(roles) } pub async fn fetch_one( conn: &mut Conn, role_uuid: Uuid, ) -> Result { use roles::dsl; let role: Role = dsl::roles .filter(dsl::uuid.eq(role_uuid)) .select(Role::as_select()) .get_result(conn) .await?; Ok(role) } pub async fn new( conn: &mut Conn, guild_uuid: Uuid, name: String, ) -> Result { let role_uuid = Uuid::now_v7(); let role = Role { uuid: role_uuid, guild_uuid, name, color: 16777215, position: 0, permissions: 0, }; insert_into(roles::table) .values(role.clone()) .execute(conn) .await?; Ok(role) } } #[derive(Queryable, Selectable, Insertable)] #[diesel(table_name = guild_members)] #[diesel(check_for_backend(diesel::pg::Pg))] pub struct Member { pub uuid: Uuid, pub nickname: Option, pub user_uuid: Uuid, pub guild_uuid: Uuid, } impl Member { async fn count(conn: &mut Conn, guild_uuid: Uuid) -> Result { use guild_members::dsl; let count: i64 = dsl::guild_members .filter(dsl::guild_uuid.eq(guild_uuid)) .count() .get_result(conn) .await?; Ok(count) } pub async fn fetch_one( conn: &mut Conn, user_uuid: Uuid, guild_uuid: Uuid, ) -> Result { use guild_members::dsl; let member: Member = dsl::guild_members .filter(dsl::user_uuid.eq(user_uuid)) .filter(dsl::guild_uuid.eq(guild_uuid)) .select(Member::as_select()) .get_result(conn) .await?; Ok(member) } pub async fn new( conn: &mut Conn, user_uuid: Uuid, guild_uuid: Uuid, ) -> Result { let member_uuid = Uuid::now_v7(); let member = Member { uuid: member_uuid, guild_uuid, user_uuid, nickname: None, }; insert_into(guild_members::table) .values(member) .execute(conn) .await?; Ok(Self { uuid: member_uuid, nickname: None, user_uuid, guild_uuid, }) } } #[derive(Clone, Serialize, Queryable, Selectable, Insertable)] #[diesel(table_name = messages)] #[diesel(check_for_backend(diesel::pg::Pg))] pub struct Message { uuid: Uuid, channel_uuid: Uuid, user_uuid: Uuid, message: String, } /// Server invite struct #[derive(Clone, Serialize, Queryable, Selectable, Insertable)] pub struct Invite { /// case-sensitive alphanumeric string with a fixed length of 8 characters, can be up to 32 characters for custom invites id: String, /// User that created the invite user_uuid: Uuid, /// UUID of the guild that the invite belongs to pub guild_uuid: Uuid, } impl Invite { pub async fn fetch_one(conn: &mut Conn, invite_id: String) -> Result { use invites::dsl; let invite: Invite = dsl::invites .filter(dsl::id.eq(invite_id)) .select(Invite::as_select()) .get_result(conn) .await?; Ok(invite) } } #[derive(Deserialize)] pub struct StartAmountQuery { pub start: Option, pub amount: Option, }