wicrs_api 0.2.1

Client API for wirc_server.
use config::{ClientBuilderConfig, ClientConfig};
use reqwest::{header::HeaderMap, StatusCode};
pub use result::{Error, Result};
use std::{convert::TryInto, fmt::Display, str::FromStr};
use wicrs_server::{
    auth::{IDToken, Service},
    channel::{Channel, Message},
    get_system_millis,
    hub::{Hub, HubMember},
    permission::{ChannelPermission, HubPermission, PermissionSetting},
    user::{GenericUser, User},
    ApiError, ID,
};
pub use wicrs_server;

pub mod config;
pub mod result;
#[macro_use]
mod macros;

pub struct Client {
    pub server_url: String,
    pub user_id: ID,
    pub token_expires: u128,
    client: reqwest::Client,
}

pub struct ClientBuilder {
    server_url: String,
    user_id: Option<ID>,
    auth_token: Option<String>,
    token_expiry: Option<u128>,
    auth_service: Service,
}

impl Client {
    pub fn from_config(config: ClientConfig) -> Result<Self> {
        if get_system_millis() > config.token_expires {
            return Err(Error::TokenExpired);
        } else {
            let mut header_map = HeaderMap::new();
            let header_value: reqwest::header::HeaderValue =
                format!("{}:{}", &config.user_id, config.auth_token)
                    .parse()
                    .map_err(|_| Error::ReqwestClientBuild)?;
            header_map.insert(reqwest::header::AUTHORIZATION, header_value);
            let reqwest_client = reqwest::Client::builder()
                .redirect(reqwest::redirect::Policy::none())
                .default_headers(header_map)
                .build()
                .map_err(|_| Error::ReqwestClientBuild)?;
            return Ok(Self {
                server_url: config.server_url,
                user_id: config.user_id,
                token_expires: config.token_expires,
                client: reqwest_client,
            });
        }
    }
}

impl Client {
    pub async fn invalidate_tokens(&self) -> Result<()> {
        get!("invalidate_tokens", self)
    }

    pub async fn get_user(&self) -> Result<User> {
        get!("user", User, self)
    }

    pub async fn get_user_by_id(&self, user_id: &ID) -> Result<GenericUser> {
        get!(format!("user/{}", user_id), GenericUser, self)
    }

    pub async fn change_username<S: Into<String>>(&self, new_name: S) -> Result<String> {
        put!(
            format!("user/change_username/{}", new_name.into()),
            String,
            self
        )
    }

    pub async fn create_hub<S: Into<String>>(&self, name: S) -> Result<ID> {
        post!(format!("hub/create/{}", name.into()), ID, self)
    }

    pub async fn get_hub(&self, hub_id: &ID) -> Result<Hub> {
        get!(format!("hub/{}", hub_id), Hub, self)
    }

    pub async fn delete_hub(&self, hub_id: &ID) -> Result<()> {
        req!(format!("hub/{}", hub_id), delete, self)
    }

    pub async fn rename_hub<S: Into<String>>(&self, hub_id: &ID, new_name: S) -> Result<String> {
        put!(
            format!("hub/rename/{}/{}", hub_id, new_name.into()),
            String,
            self
        )
    }

    pub async fn is_banned_from_hub(&self, hub_id: &ID, user_id: &ID) -> Result<bool> {
        get!(
            format!("member/{}/{}/is_banned", hub_id, user_id),
            bool,
            self
        )
    }

    pub async fn hub_member_is_muted(&self, hub_id: &ID, user_id: &ID) -> Result<bool> {
        get!(
            format!("member/{}/{}/is_muted", hub_id, user_id),
            bool,
            self
        )
    }

    pub async fn get_hub_member(&self, hub_id: &ID, user_id: &ID) -> Result<HubMember> {
        get!(format!("member/{}/{}", hub_id, user_id), HubMember, self)
    }

    pub async fn join_hub(&self, hub_id: &ID) -> Result<()> {
        post!(format!("hub/join/{}", hub_id), self)
    }

    pub async fn leave_hub(&self, hub_id: &ID) -> Result<()> {
        post!(format!("hub/leave/{}", hub_id), self)
    }

    member_fns! {
        (kick_user, kick), (ban_user, ban), (unban_user, unban), (mute_user, mute), (unmute_user, unmute)
    }

    pub async fn change_nickname<S: Into<String>>(
        &self,
        hub_id: &ID,
        new_name: S,
    ) -> Result<String> {
        put!(
            format!("member/change_nickname/{}/{}", hub_id, new_name.into()),
            String,
            self
        )
    }

    pub async fn create_channel<S: Into<String>>(&self, hub_id: &ID, name: S) -> Result<ID> {
        put!(
            format!("channel/create/{}/{}", hub_id, name.into()),
            ID,
            self
        )
    }

    pub async fn get_channel(&self, hub_id: &ID, channel_id: &ID) -> Result<Channel> {
        get!(format!("channel/{}/{}", hub_id, channel_id), Channel, self)
    }

    pub async fn delete_channel(&self, hub_id: &ID, channel_id: &ID) -> Result<()> {
        req!(format!("channel/{}/{}", hub_id, channel_id), delete, self)
    }

    pub async fn send_message<D: Display>(
        &self,
        hub_id: &ID,
        channel_id: &ID,
        message: D,
    ) -> Result<ID> {
        post!(
            format!("message/send/{}/{}/{}", hub_id, channel_id, message),
            ID,
            self
        )
    }

    pub async fn get_message(
        &self,
        hub_id: &ID,
        channel_id: &ID,
        message_id: &ID,
    ) -> Result<Message> {
        get!(
            format!("message/{}/{}/{}", hub_id, channel_id, message_id),
            Message,
            self
        )
    }

    pub async fn get_messages(
        &self,
        hub_id: &ID,
        channel_id: &ID,
        from: Option<u128>,
        to: Option<u128>,
        invert: Option<bool>,
        max: Option<u128>,
    ) -> Result<Vec<Message>> {
        let mut query_str = String::new();
        if let Some(from) = from {
            query_str = format!("?from={}", from);
        }
        if let Some(to) = to {
            query_str = format!("{}&to={}", query_str, to);
        }
        if let Some(invert) = invert {
            query_str = format!("{}&invert={}", query_str, invert);
        }
        if let Some(max) = max {
            query_str = format!("{}&max={}", query_str, max);
        }
        type Messages = Vec<Message>;
        get!(
            format!("message/{}/{}{}", hub_id, channel_id, query_str),
            Messages,
            self
        )
    }

    pub async fn set_user_hub_permission(
        &self,
        hub_id: &ID,
        user_id: &ID,
        permission: HubPermission,
        setting: &PermissionSetting,
    ) -> Result<()> {
        post!(
            format!(
                "member/set_hub_permission/{}/{}/{}?setting={}",
                hub_id,
                user_id,
                permission,
                serde_json::to_string(setting).map_err(|_| Error::SerializeFailed)?
            ),
            self
        )
    }

    pub async fn set_user_channel_permission(
        &self,
        hub_id: &ID,
        channel_id: &ID,
        user_id: &ID,
        permission: ChannelPermission,
        setting: &PermissionSetting,
    ) -> Result<()> {
        post!(
            format!(
                "member/set_hub_permission/{}/{}/{}/{}?setting={}",
                hub_id,
                channel_id,
                user_id,
                permission,
                serde_json::to_string(setting).map_err(|_| Error::SerializeFailed)?
            ),
            self
        )
    }
}

impl ClientBuilder {
    pub fn new<S: Into<String>>(server_url: S, auth_service: Service) -> Self {
        Self {
            server_url: server_url.into(),
            user_id: None,
            auth_token: None,
            token_expiry: None,
            auth_service,
        }
    }

    pub fn from_config(config: ClientBuilderConfig) -> Self {
        Self::new(config.server_url, config.auth_service)
    }

    pub async fn start_login(&self) -> Result<String> {
        let client = reqwest::Client::builder()
            .redirect(reqwest::redirect::Policy::none())
            .build()
            .map_err(|_| Error::ReqwestClientBuild)?;
        let response = client
            .get(&format!("{}/login/{}", self.server_url, self.auth_service))
            .send()
            .await
            .map_err(|_| Error::Connection)?;
        if response.status() == StatusCode::FOUND {
            if let Some(header) = response.headers().get(reqwest::header::LOCATION) {
                return Ok(header
                    .to_str()
                    .map_err(|_| Error::UnexpectedResponse)?
                    .to_string());
            }
        }
        Err(Error::UnexpectedResponse)
    }

    pub fn finish_login(&mut self, id_token: IDToken, expiry: u128) {
        self.token_expiry = Some(expiry);
        self.user_id = Some(id_token.id);
        self.auth_token = Some(id_token.token);
    }

    pub async fn build(self) -> Result<Client> {
        Client::from_config(self.try_into()?)
    }
}