diff --git a/migrations/2025-05-26-181536_add_channel_ordering/down.sql b/migrations/2025-05-26-181536_add_channel_ordering/down.sql new file mode 100644 index 0000000..0a70d35 --- /dev/null +++ b/migrations/2025-05-26-181536_add_channel_ordering/down.sql @@ -0,0 +1,2 @@ +-- This file should undo anything in `up.sql` +ALTER TABLE channels DROP COLUMN is_above; diff --git a/migrations/2025-05-26-181536_add_channel_ordering/up.sql b/migrations/2025-05-26-181536_add_channel_ordering/up.sql new file mode 100644 index 0000000..e18e5e2 --- /dev/null +++ b/migrations/2025-05-26-181536_add_channel_ordering/up.sql @@ -0,0 +1,2 @@ +-- Your SQL goes here +ALTER TABLE channels ADD COLUMN is_above UUID UNIQUE REFERENCES channels(uuid) DEFAULT NULL; diff --git a/src/api/v1/auth/refresh.rs b/src/api/v1/auth/refresh.rs index 303748a..fceabf5 100644 --- a/src/api/v1/auth/refresh.rs +++ b/src/api/v1/auth/refresh.rs @@ -59,14 +59,9 @@ pub async fn res(req: HttpRequest, data: web::Data) -> Result 1987200 { - let new_refresh_token = generate_refresh_token(); + let new_refresh_token = generate_refresh_token()?; - if new_refresh_token.is_err() { - error!("{}", new_refresh_token.unwrap_err()); - return Ok(HttpResponse::InternalServerError().finish()); - } - - let new_refresh_token = new_refresh_token.unwrap(); + let new_refresh_token = new_refresh_token; match update(refresh_tokens::table) .filter(rdsl::token.eq(&refresh_token)) diff --git a/src/api/v1/servers/mod.rs b/src/api/v1/servers/mod.rs index 76a4c16..91980e3 100644 --- a/src/api/v1/servers/mod.rs +++ b/src/api/v1/servers/mod.rs @@ -63,7 +63,7 @@ pub async fn get( let amount = request_query.amount.unwrap_or(10); - check_access_token(auth_header, &mut data.pool.get().await.unwrap()).await?; + check_access_token(auth_header, &mut data.pool.get().await?).await?; let guilds = Guild::fetch_amount(&data.pool, start, amount).await?; diff --git a/src/api/v1/servers/uuid/channels/mod.rs b/src/api/v1/servers/uuid/channels/mod.rs index 0c515ef..c3640fe 100644 --- a/src/api/v1/servers/uuid/channels/mod.rs +++ b/src/api/v1/servers/uuid/channels/mod.rs @@ -3,7 +3,7 @@ use crate::{ api::v1::auth::check_access_token, error::Error, structs::{Channel, Member}, - utils::get_auth_header, + utils::{get_auth_header, order_channels}, }; use ::uuid::Uuid; use actix_web::{HttpRequest, HttpResponse, get, post, web}; @@ -43,10 +43,16 @@ pub async fn get( let channels = Channel::fetch_all(&data.pool, guild_uuid).await?; - data.set_cache_key(format!("{}_channels", guild_uuid), channels.clone(), 1800) - .await?; + let channels_ordered = order_channels(channels).await?; - Ok(HttpResponse::Ok().json(channels)) + data.set_cache_key( + format!("{}_channels", guild_uuid), + channels_ordered.clone(), + 1800, + ) + .await?; + + Ok(HttpResponse::Ok().json(channels_ordered)) } #[post("{uuid}/channels")] @@ -76,7 +82,7 @@ pub async fn create( channel_info.name.clone(), channel_info.description.clone(), ) - .await; + .await?; - Ok(HttpResponse::Ok().json(channel.unwrap())) + Ok(HttpResponse::Ok().json(channel)) } diff --git a/src/api/v1/servers/uuid/channels/uuid/mod.rs b/src/api/v1/servers/uuid/channels/uuid/mod.rs index 54f90a7..b1c749f 100644 --- a/src/api/v1/servers/uuid/channels/uuid/mod.rs +++ b/src/api/v1/servers/uuid/channels/uuid/mod.rs @@ -64,7 +64,7 @@ pub async fn delete( let channel: Channel; if let Ok(cache_hit) = data.get_cache_key(format!("{}", channel_uuid)).await { - channel = serde_json::from_str(&cache_hit).unwrap(); + channel = serde_json::from_str(&cache_hit)?; data.del_cache_key(format!("{}", channel_uuid)).await?; } else { diff --git a/src/api/v1/servers/uuid/channels/uuid/socket.rs b/src/api/v1/servers/uuid/channels/uuid/socket.rs index 744f017..a90cb86 100644 --- a/src/api/v1/servers/uuid/channels/uuid/socket.rs +++ b/src/api/v1/servers/uuid/channels/uuid/socket.rs @@ -42,7 +42,7 @@ pub async fn echo( // Return channel cache or result from psql as `channel` variable if let Ok(cache_hit) = data.get_cache_key(format!("{}", channel_uuid)).await { - channel = serde_json::from_str(&cache_hit).unwrap() + channel = serde_json::from_str(&cache_hit)? } else { channel = Channel::fetch_one(&mut conn, channel_uuid).await?; diff --git a/src/api/v1/servers/uuid/roles/mod.rs b/src/api/v1/servers/uuid/roles/mod.rs index fe25d39..db6c7cf 100644 --- a/src/api/v1/servers/uuid/roles/mod.rs +++ b/src/api/v1/servers/uuid/roles/mod.rs @@ -62,7 +62,7 @@ pub async fn create( let guild_uuid = path.into_inner().0; - let mut conn = data.pool.get().await.unwrap(); + let mut conn = data.pool.get().await?; let uuid = check_access_token(auth_header, &mut conn).await?; diff --git a/src/error.rs b/src/error.rs index fa9524f..3907f7c 100644 --- a/src/error.rs +++ b/src/error.rs @@ -60,6 +60,8 @@ pub enum Error { BadRequest(String), #[error("{0}")] Unauthorized(String), + #[error("{0}")] + InternalServerError(String), } impl ResponseError for Error { diff --git a/src/schema.rs b/src/schema.rs index 33935f7..8a85a2e 100644 --- a/src/schema.rs +++ b/src/schema.rs @@ -27,6 +27,7 @@ diesel::table! { name -> Varchar, #[max_length = 500] description -> Nullable, + is_above -> Nullable, } } diff --git a/src/structs.rs b/src/structs.rs index c86bc5b..b423b20 100644 --- a/src/structs.rs +++ b/src/structs.rs @@ -5,12 +5,18 @@ use diesel::{ update, }; use diesel_async::{RunQueryDsl, pooled_connection::AsyncDieselConnectionManager}; +use log::debug; use serde::{Deserialize, Serialize}; use tokio::task; use url::Url; use uuid::Uuid; -use crate::{Conn, Data, error::Error, schema::*, utils::image_check}; +use crate::{ + Conn, Data, + error::Error, + schema::*, + utils::{image_check, order_channels}, +}; fn load_or_empty( query_result: Result, diesel::result::Error>, @@ -22,7 +28,7 @@ fn load_or_empty( } } -#[derive(Queryable, Selectable, Insertable, Clone)] +#[derive(Queryable, Selectable, Insertable, Clone, Debug)] #[diesel(table_name = channels)] #[diesel(check_for_backend(diesel::pg::Pg))] struct ChannelBuilder { @@ -30,6 +36,7 @@ struct ChannelBuilder { guild_uuid: Uuid, name: String, description: Option, + is_above: Option, } impl ChannelBuilder { @@ -48,21 +55,23 @@ impl ChannelBuilder { guild_uuid: self.guild_uuid, name: self.name, description: self.description, + is_above: self.is_above, permissions: channel_permission, }) } } -#[derive(Serialize, Deserialize, Clone)] +#[derive(Serialize, Deserialize, Clone, Debug)] pub struct Channel { pub uuid: Uuid, pub guild_uuid: Uuid, name: String, description: Option, + pub is_above: Option, pub permissions: Vec, } -#[derive(Serialize, Deserialize, Clone, Queryable, Selectable)] +#[derive(Serialize, Deserialize, Clone, Queryable, Selectable, Debug)] #[diesel(table_name = channel_permissions)] #[diesel(check_for_backend(diesel::pg::Pg))] pub struct ChannelPermission { @@ -118,24 +127,47 @@ impl Channel { let channel_uuid = Uuid::now_v7(); + let channels = Self::fetch_all(&data.pool, guild_uuid).await?; + + debug!("{:?}", channels); + + let channels_ordered = order_channels(channels).await?; + + debug!("{:?}", channels_ordered); + + let last_channel = channels_ordered.last(); + let new_channel = ChannelBuilder { uuid: channel_uuid, guild_uuid, name: name.clone(), description: description.clone(), + is_above: None, }; + debug!("New Channel: {:?}", new_channel); + insert_into(channels::table) - .values(new_channel) + .values(new_channel.clone()) .execute(&mut conn) .await?; + if let Some(old_last_channel) = last_channel { + use channels::dsl; + update(channels::table) + .filter(dsl::uuid.eq(old_last_channel.uuid)) + .set(dsl::is_above.eq(new_channel.uuid)) + .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, + is_above: None, permissions: vec![], }; diff --git a/src/utils.rs b/src/utils.rs index 631b003..e68f38a 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -9,7 +9,7 @@ use hex::encode; use redis::RedisError; use serde::Serialize; -use crate::{Data, error::Error}; +use crate::{Data, error::Error, structs::Channel}; pub fn get_auth_header(headers: &HeaderMap) -> Result<&str, Error> { let auth_token = headers.get(actix_web::http::header::AUTHORIZATION); @@ -119,6 +119,28 @@ pub fn image_check(icon: BytesMut) -> Result { )) } +pub async fn order_channels(mut channels: Vec) -> Result, Error> { + let mut ordered = Vec::new(); + + // Find head + let head_pos = channels + .iter() + .position(|channel| !channels.iter().any(|i| i.is_above == Some(channel.uuid))); + + if let Some(pos) = head_pos { + ordered.push(channels.swap_remove(pos)); + + while let Some(next_pos) = channels + .iter() + .position(|channel| Some(channel.uuid) == ordered.last().unwrap().is_above) + { + ordered.push(channels.swap_remove(next_pos)); + } + } + + Ok(ordered) +} + impl Data { pub async fn set_cache_key( &self,