wirc_server 0.3.0

A server/channel based chat handler backend.
Documentation
use reqwest::StatusCode;
use std::{
    sync::Arc,
    time::{SystemTime, UNIX_EPOCH},
};
use tokio::sync::Mutex;

#[cfg(test)]
#[macro_use]
extern crate serial_test;

#[macro_use]
pub mod macros;

pub mod auth;
pub mod channel;
pub mod config;
pub mod hub;
pub mod permission;
pub mod user;

use auth::Auth;

use uuid::Uuid;

use warp::{filters::BoxedFilter, Filter, Reply};

#[derive(Eq, PartialEq, Debug)]
pub enum JsonLoadError {
    ReadFile,
    Deserialize,
}

#[derive(Eq, PartialEq, Debug)]
pub enum JsonSaveError {
    WriteFile,
    Serialize,
    Directory,
}

pub fn unexpected_response() -> warp::http::Response<warp::hyper::Body> {
    warp::reply::with_status("Unexpected error.", StatusCode::INTERNAL_SERVER_ERROR).into_response()
}

pub fn bad_auth_response() -> warp::http::Response<warp::hyper::Body> {
    warp::reply::with_status("Invalid authentication details.", StatusCode::FORBIDDEN)
        .into_response()
}

pub fn account_not_found_response() -> warp::http::Response<warp::hyper::Body> {
    warp::reply::with_status("Could not find that account.", StatusCode::NOT_FOUND).into_response()
}

#[derive(Debug)]
pub enum ApiActionError {
    HubNotFound,
    ChannelNotFound,
    NoPermission,
    NotInHub,
    WriteFileError,
    OpenFileError,
    UserNotFound,
    BadNameCharacters,
}

static USER_AGENT_STRING: &str = "wirc_server";
static NAME_ALLOWED_CHARS: &str =
    " .,_-0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";

pub async fn run() {
    let config = config::load_config();
    warp::serve(filter(Auth::from_config().await).await)
        .run((config.listen, config.port))
        .await;
}

pub async fn filter(auth: Auth) -> BoxedFilter<(impl Reply,)> {
    let auth = Arc::new(Mutex::new(auth));
    let api_v1 = v1_api(auth.clone());
    let api = warp::any().and(warp::path("api")).and(api_v1);
    api.or(warp::any().map(|| {
        warp::reply::with_status(
            "Not found. Make sure you provided all of the required parameters.",
            StatusCode::NOT_FOUND,
        )
    }))
    .boxed()
}

pub async fn testing() -> (BoxedFilter<(impl Reply,)>, String, String) {
    let auth = Auth::for_testing().await;
    (filter(auth.0).await, auth.1, auth.2)
}

pub fn get_system_millis() -> u128 {
    SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .unwrap()
        .as_millis()
}

pub fn is_valid_username(name: &str) -> bool {
    name.len() > 0 && name.len() < 32 && name.chars().all(|c| NAME_ALLOWED_CHARS.contains(c))
}

pub type ID = Uuid;
pub fn new_id() -> ID {
    uuid::Uuid::new_v4()
}

fn v1_api(auth_manager: Arc<Mutex<Auth>>) -> BoxedFilter<(impl Reply,)> {
    let guild_api = warp::path("hubs").and(hub::api_v1(auth_manager.clone()));
    let auth_api = auth::api_v1(auth_manager.clone());
    let user_api = user::api_v1(auth_manager.clone());
    warp::path("v1")
        .and(auth_api.or(user_api).or(guild_api))
        .boxed()
}

#[cfg(test)]
mod tests {
    use super::is_valid_username;

    #[test]
    fn valid_username_check() {
        assert!(is_valid_username("a"));
        assert!(is_valid_username("Test_test tHAt-tester."));
        assert!(is_valid_username("1234567890"));
        assert!(is_valid_username("l33t 5p34k"));
        assert!(!is_valid_username(""));
        assert!(!is_valid_username("Test! @thing"));
        assert!(!is_valid_username("123456789111315171921232527293133"));
    }
}