essence 0.7.0

Essential models and database logic for the Adapt chat platform.
Documentation
use crate::{
    bincode_impl::BincodeType,
    error::{ErrIntoExt, Result},
    models::{ChannelType, Permissions, User, UserFlags},
};
use deadpool_redis::{redis::AsyncCommands, Config, Connection, Pool, Runtime};
use std::sync::OnceLock;

static POOL: OnceLock<Pool> = OnceLock::new();

pub trait AsRefThreadSafe<T: ?Sized> = AsRef<T> + Send + Sync;

#[derive(Clone, bincode::Encode, bincode::Decode)]
pub struct ChannelInspection {
    pub guild_id: Option<u64>,
    pub owner_id: Option<u64>,
    pub channel_type: ChannelType,
}

pub(crate) fn connect(url: &str) {
    POOL.set(
        Config::from_url(url)
            .create_pool(Some(Runtime::Tokio1))
            .unwrap(),
    )
    .unwrap_or_else(|_| panic!("Failed to set `POOL`"));
}

async fn get_con() -> Result<Connection> {
    unsafe { Ok(POOL.get().unwrap_unchecked().get().await?) }
}

pub async fn user_info_for_token(
    token: impl AsRefThreadSafe<str>,
) -> Result<Option<(u64, UserFlags)>> {
    Ok(get_con()
        .await?
        .hget::<_, _, Option<BincodeType<(u64, UserFlags)>>>("essence-tokens", token.as_ref())
        .await?
        .map(|v| v.0))
}

pub async fn cache_token(
    token: impl AsRefThreadSafe<str>,
    user_id: u64,
    flags: UserFlags,
) -> Result<()> {
    let () = get_con()
        .await?
        .hset(
            "essence-tokens",
            token.as_ref(),
            BincodeType((user_id, flags)),
        )
        .await?;

    Ok(())
}

pub async fn invalidate_token(token: String) -> Result<()> {
    get_con()
        .await?
        .hdel("essence-tokens", token)
        .await
        .err_into()
}

pub async fn invalidate_tokens_for(user_id: u64) -> Result<()> {
    let mut con = get_con().await?;

    let tokens = con
        .hgetall::<_, Vec<(String, BincodeType<(u64, UserFlags)>)>>("essence-tokens")
        .await?
        .into_iter()
        .filter_map(|(token, x)| {
            let user = x.0 .0;

            if user == user_id {
                Some(token)
            } else {
                None
            }
        })
        .collect::<Vec<String>>();

    if !tokens.is_empty() {
        let () = con.hdel("essence-tokens", tokens).await?;
    }
    Ok(())
}

pub async fn update_user(user: User) -> Result<()> {
    get_con()
        .await?
        .hset("essence-users", user.id, BincodeType(user))
        .await
        .err_into()
}

pub async fn user(user_id: u64) -> Result<Option<User>> {
    Ok(get_con()
        .await?
        .hget::<_, _, Option<BincodeType<User>>>("essence-users", user_id)
        .await?
        .map(|u| u.0))
}

pub async fn remove_user(user_id: u64) -> Result<()> {
    get_con()
        .await?
        .hdel("essence-users", user_id)
        .await
        .err_into()
}

pub async fn update_channel(channel_id: u64, inspection: ChannelInspection) -> Result<()> {
    get_con()
        .await?
        .hset("essence-channels", channel_id, BincodeType(inspection))
        .await
        .err_into()
}

pub async fn inspection_for_channel(channel_id: u64) -> Result<Option<ChannelInspection>> {
    Ok(get_con()
        .await?
        .hget::<_, _, Option<BincodeType<ChannelInspection>>>("essence-channels", channel_id)
        .await?
        .map(|v| v.0))
}

pub async fn remove_channel(channel_id: u64) -> Result<()> {
    get_con()
        .await?
        .hdel("essence-channels", channel_id)
        .await
        .err_into()
}

pub async fn remove_guild(guild_id: u64) -> Result<()> {
    let mut con = get_con().await?;

    let keys = con
        .keys::<_, Vec<String>>(format!("essence-{guild_id}-*"))
        .await?;
    let () = con.del(keys).await?;

    con.srem("essence-guilds", guild_id).await.err_into()
}

pub async fn insert_guild(guild_id: u64) -> Result<()> {
    get_con()
        .await?
        .sadd("essence-guilds", guild_id)
        .await
        .err_into()
}

pub async fn insert_guilds(guild_ids: impl AsRefThreadSafe<[u64]>) -> Result<()> {
    get_con()
        .await?
        .sadd("essence-guilds", guild_ids.as_ref())
        .await
        .err_into()
}

pub async fn guild_exist(guild_id: u64) -> Result<Option<()>> {
    Ok(get_con()
        .await?
        .sismember::<_, _, bool>("essence-guilds", guild_id)
        .await?
        .then_some(()))
}

pub async fn is_member_of_guild(guild_id: u64, user_id: u64) -> Result<Option<()>> {
    Ok(get_con()
        .await?
        .sismember::<_, _, bool>(format!("essence-{guild_id}-members"), user_id)
        .await?
        .then_some(()))
}

pub async fn remove_member_from_guild(guild_id: u64, user_id: u64) -> Result<()> {
    delete_permissions_for_user(guild_id, user_id).await.ok();
    get_con()
        .await?
        .srem(format!("essence-{guild_id}-members"), user_id)
        .await
        .err_into()
}

pub async fn update_member_of_guild(guild_id: u64, user_id: u64) -> Result<()> {
    get_con()
        .await?
        .sadd(format!("essence-{guild_id}-members"), user_id)
        .await
        .err_into()
}

pub async fn update_members_of_guild(
    guild_id: u64,
    user_ids: impl AsRefThreadSafe<[u64]>,
) -> Result<()> {
    get_con()
        .await?
        .sadd(format!("essence-{guild_id}-members"), user_ids.as_ref())
        .await
        .err_into()
}

pub async fn update_owner_of_guild(guild_id: u64, user_id: u64) -> Result<()> {
    get_con()
        .await?
        .set(format!("essence-{guild_id}-owner"), user_id)
        .await
        .err_into()
}

pub async fn owner_of_guild(guild_id: u64) -> Result<Option<u64>> {
    get_con()
        .await?
        .get(format!("essence-{guild_id}-owner"))
        .await
        .err_into()
}

pub async fn update_permissions_for(
    guild_id: u64,
    user_id: u64,
    channel_id: Option<u64>,
    permissions: Permissions,
) -> Result<()> {
    get_con()
        .await?
        .hset(
            format!("essence-{guild_id}-{user_id}-perm"),
            channel_id.unwrap_or(0),
            permissions.bits(),
        )
        .await
        .err_into()
}

pub async fn permissions_for(
    guild_id: u64,
    user_id: u64,
    channel_id: Option<u64>,
) -> Result<Option<Permissions>> {
    Ok(get_con()
        .await?
        .hget::<_, _, Option<i64>>(
            format!("essence-{guild_id}-{user_id}-perm"),
            channel_id.unwrap_or(0),
        )
        .await?
        .map(Permissions::from_bits_truncate))
}

pub async fn delete_permissions_for_user(guild_id: u64, user_id: u64) -> Result<()> {
    get_con()
        .await?
        .del(format!("essence-{guild_id}-{user_id}-perm"))
        .await
        .err_into()
}

pub async fn delete_permissions_for_channel(guild_id: u64, channel_id: u64) -> Result<()> {
    let mut con = get_con().await?;
    let keys = con
        .keys::<_, Vec<String>>(format!("essence-{guild_id}-*-perm"))
        .await?;

    con.hdel(keys, channel_id).await.err_into()
}

pub async fn delete_permissions_for_user_in_channel(
    guild_id: u64,
    user_id: u64,
    channel_id: Option<u64>,
) -> Result<()> {
    get_con()
        .await?
        .hdel(
            format!("essence-{guild_id}-{user_id}-perm"),
            channel_id.unwrap_or(0),
        )
        .await
        .err_into()
}

pub async fn clear_member_permissions(guild_id: u64) -> Result<()> {
    let mut con = get_con().await?;
    let keys = con
        .keys::<_, Vec<String>>(format!("essence-{guild_id}-*-perm"))
        .await?;

    con.del(keys).await.err_into()
}