comment_app_frontend 0.1.3

A Comment App Front End Server
Documentation
use std::collections::HashMap;
use std::sync::RwLock;
use lazy_static::*;
use config::{Config, File, FileFormat};
use handlebars::Handlebars;
use serde_json::value::{Map, Value as Json};
use reqwest::Url;
use authorization::Authorization;
use ::log::*;
use chrono::{DateTime, Local};

pub mod comment_server;
pub mod log;
pub mod filters;
pub mod handlers;
pub mod models;

// initialize: application settings 
lazy_static!{
    // pull-in comments specific settings from Settings.toml 
    static ref SETTINGS: HashMap<String, String> = get_settings();
    static ref TEMPLATES: Handlebars<'static> = get_templates();
    static ref AUTHZN: RwLock<Authorization> = get_authorization();
    static ref SESSION_ID: RwLock<String> = RwLock::new( String::new() );
}

// Extract Todo Specific settings from Settings.toml
fn get_settings() -> HashMap<String, String> {
    let mut config = Config::default();
    config.merge(File::new("Settings", FileFormat::Toml)).unwrap();
    let settings = config.try_into::<HashMap<String, String>>().unwrap(); // Deserialize entire file contents
    settings
}

pub fn config(key: &str) -> String {
    SETTINGS.get(key).unwrap().to_string()
}

fn get_templates() -> Handlebars<'static> {
    let mut hb = Handlebars::new();
    hb.register_template_file("index_page", "./templates/index_page.hbs").unwrap();
    hb.register_template_file("comments_page", "./templates/comments_page.hbs").unwrap();
    hb.register_template_file("reply_page_partial", "./templates/reply_page_partial.hbs").unwrap();
    hb    
}
fn get_authorization() -> RwLock<Authorization> {
    let authzn = Authorization::load( &config("resource_permissions"), &config("user_roles") );
    RwLock::new(authzn)
}

fn set_session_id(sid: String) {
    info!("setting session id into lazy static.........{:?}", &sid);
    let mut session_id = SESSION_ID.write().unwrap();
    *session_id = sid;
}

fn get_session_id() -> String {
    let sid = SESSION_ID.read().unwrap();
    sid.to_string()
}

pub fn allows_add(cid: &str, res: &str, data: &Map<String, Json>) -> bool {
    allows("add", cid, res, data)
}
pub fn allows_view(cid: &str, res: &str, data: &Map<String, Json>) -> bool {
    allows("view", cid, res, data)
}
pub fn allows_edit(cid: &str, res: &str, data: &Map<String, Json>) -> bool {
    allows("edit", cid, res, data)
}
pub fn allows_delete(cid: &str, res: &str, data: &Map<String, Json>) -> bool {
    allows("delete", cid, res, data)
}

fn allows(op: &str, cid: &str, res: &str, data: &Map<String, Json>) -> bool {
    debug!("Authzn: op: {:?}, cid: {:?}, resource: {:?}.............", op, cid, res);
    // isolate read locks and write locks using blocks
    { // block for read lock 
        let authzn_r = AUTHZN.read().unwrap();
        if authzn_r.has_permissions_for(cid) {  // if permissions are readily available on user 'cid', get it
            return match op {
                "add" => authzn_r.allows_add(cid, res, data),
                "view" => authzn_r.allows_view(cid, res, data),
                "edit" => authzn_r.allows_edit(cid, res, data),
                "delete" => authzn_r.allows_delete(cid, res, data),
                _ => false
            }
        }
    }   // read lock is dropped here
    // write lock uses the function block itself as it happens to be a last lock in this function
    let mut authzn_w = AUTHZN.write().unwrap(); // if no permissions available for user, open lock in write mode
    authzn_w.set_permissions_for(cid); // let derive permissions from user's roles and cache it in hashmap for future references
    match op {
        "add" => authzn_w.allows_add(cid, res, data),
        "view" => authzn_w.allows_view(cid, res, data),
        "edit" => authzn_w.allows_edit(cid, res, data),
        "delete" => authzn_w.allows_delete(cid, res, data),
        _ => false
    }
}

pub fn render(template_name: &str, data: &Map<String, Json>) -> String {
    TEMPLATES.render(template_name, data)
        .unwrap_or_else(|err| err.to_string())
}

pub fn comments_url() -> Url {
    let ip = config("backend_server_ip_address");
    let path = config("backend_server_comments_path");
    let mut url = Url::parse(&ip).unwrap();
    url.set_path(&path);    
    debug!("comments_url: {}", url);
    url
}
 
pub fn comments_total_url() -> Url {
    let ip = config("backend_server_ip_address");
    let path = config("backend_server_comments_total_path");
    let mut url = Url::parse(&ip).unwrap();
    url.set_path(&path);    
    debug!("comments_total_url: {}", url);
    url
}

pub fn replies_url() -> Url {
    let ip = config("backend_server_ip_address");
    let path = config("backend_server_replies_path");
    let mut url = Url::parse(&ip).unwrap();
    url.set_path(&path);    
    debug!("replies_url: {}", url);
    url
}

pub fn replies_url_with(addl_path: &str) -> Url {
    let ip = config("backend_server_ip_address");
    let mut path = config("backend_server_replies_path");
    if addl_path.len() > 0 {
        path = path.to_owned() + "/" + addl_path;
    }
    let mut url = Url::parse(&ip).unwrap();
    url.set_path(&path);    
    debug!("replies_url_with: {}", url);
    url
}

pub fn session_url() -> Url {
    let ip = config("app_server_local_ip_address");
    let path = config("app_server_session_path");
    let mut url = Url::parse(&ip).unwrap();
    url.set_path(&path);    
    info!("session_url: {}.................................................................", url);
    url
}

pub fn session_url_with(sid: &str) -> Url {
    let ip = config("app_server_local_ip_address");
    let mut path = config("app_server_session_path");
    if sid.len() > 0 {
        path = path.to_owned() + "/" + sid;
    }
    let mut url = Url::parse(&ip).unwrap();
    url.set_path(&path);    
    info!("session_url: {}.................................................................", url);
    url
}

pub fn commenter_url_with(addl_path: &str) -> Url {
    let ip = config("app_server_local_ip_address");
    let mut path = config("app_server_commenter_path");
    if addl_path.len() > 0 {
        path = path.to_owned() + "/" + addl_path;
    }
    let mut url = Url::parse(&ip).unwrap();
    url.set_path(&path);    
    debug!("commenter_url_with: {}", url);
    url
}

pub fn avatar_url_path() -> String {
    let ip = config("app_server_named_ip_address");
    let path = config("app_server_avatar_path");
    ip + &path
}

pub fn status_update_url(status: &str, cid: &str) -> Url {
    let ip = config("backend_server_ip_address");
    let mut path = config("backend_server_comment_path");
    path = path.to_owned() + "/" + status + "/" + cid;
    let mut url = Url::parse(&ip).unwrap();
    url.set_path(&path);
    debug!("status_update_url: {}", url);
    url
}

pub fn comment_msg_update_url(cid: &str) -> Url {
    let ip = config("backend_server_ip_address");
    let mut path = config("backend_server_comment_message_path");
    path = path.to_owned() + "/" + cid;
    let mut url = Url::parse(&ip).unwrap();
    url.set_path(&path);
    debug!("comment_msg_update_url: {}", url);
    url
}

pub fn comments_trash_url(uid: &str) -> Url {
    let ip = config("backend_server_ip_address");
    let mut path = config("backend_server_comments_path");
    path = path.to_owned() + "/" + uid;
    let mut url = Url::parse(&ip).unwrap();
    url.set_path(&path);
    debug!("comments_trash_url: {}", url);
    url
}

pub fn reply_msg_update_url(cid: &str) -> Url {
    let ip = config("backend_server_ip_address");
    let mut path = config("backend_server_reply_message_path");
    path = path.to_owned() + "/" + cid;
    let mut url = Url::parse(&ip).unwrap();
    url.set_path(&path);
    debug!("reply_msg_update_url: {}", url);
    url
}

pub fn replies_trash_url(uid: &str) -> Url {
    let ip = config("backend_server_ip_address");
    let mut path = config("backend_server_replies_path");
    path = path.to_owned() + "/" + uid;
    let mut url = Url::parse(&ip).unwrap();
    url.set_path(&path);
    debug!("replies_trash_url: {}", url);
    url
}

pub fn more_comments_url(count: usize) -> Url {
    let ip = config("backend_server_ip_address");
    let mut path = config("backend_server_more_comments_path");
    path = path.to_owned() + "/" + &count.to_string();
    let mut url = Url::parse(&ip).unwrap();
    url.set_path(&path);
    debug!("more_comments_url: {}", url);
    url   
}

pub fn upvote_url() -> Url {
    let ip = config("backend_server_ip_address");
    let path = config("backend_server_upvote_path");
    let mut url = Url::parse(&ip).unwrap();
    url.set_path(&path);
    debug!("upvote_url: {}", url);
    url       
}

pub fn upvotes_count_url(comment_id: &str) -> Url {
    let ip = config("backend_server_ip_address");
    let mut path = config("backend_server_upvotes_count_path");
    path = path.to_owned() + "/" + &comment_id;
    let mut url = Url::parse(&ip).unwrap();
    url.set_path(&path);
    debug!("upvotes_count_url: {}", url);
    url       
}

pub fn downvote_url() -> Url {
    let ip = config("backend_server_ip_address");
    let path = config("backend_server_downvote_path");
    let mut url = Url::parse(&ip).unwrap();
    url.set_path(&path);
    debug!("downvote_url: {}", url);
    url       
}

pub fn downvotes_count_url(comment_id: &str) -> Url {
    let ip = config("backend_server_ip_address");
    let mut path = config("backend_server_downvotes_count_path");
    path = path.to_owned() + "/" + &comment_id;
    let mut url = Url::parse(&ip).unwrap();
    url.set_path(&path);
    debug!("downvotes_count_url: {}", url);
    url       
}

pub fn time_ago(dt_str: &str) -> String {
    let dt = DateTime::parse_from_str(dt_str, "%Y-%m-%d %H:%M:%S%.9f %z").unwrap();
    let now = Local::now();
    let mut elapsed = now.timestamp_millis() - dt.timestamp_millis(); // subtract now from dt

    elapsed = elapsed / 1000; // convert from millis to seconds
    let mut plural = "";
    if elapsed > 1 { plural = "s" };
    if elapsed < 60 { return format!("{} second{} ago", &elapsed, plural); }

    elapsed = elapsed / 60; // convert to minutes
    plural = if elapsed == 1 { "" } else { "s" };
    if elapsed < 60 { return format!("{} minute{} ago", &elapsed, plural); }

    elapsed = elapsed / 60; // convert to hours
    plural = if elapsed == 1 { "" } else { "s" };
    if elapsed < 24 { return format!("{} hour{} ago", &elapsed, plural); }

    elapsed = elapsed / 24; // convert to days
    plural = if elapsed == 1 { "" } else { "s" };
    if elapsed < 7 { return format!("{} day{} ago", &elapsed, plural); }

    elapsed = elapsed / 7; // convert to weeks
    plural = if elapsed == 1 { "" } else { "s" };
    if elapsed <= 4 { return format!("{} week{} ago", &elapsed, plural); }

    let now_year = now.format("%Y").to_string();
    if dt_str.contains( &now_year ) { // check whether year now and commented year are same
        dt.format("%d %B").to_string()  // show dd MON format, if years are same
    } else {
        dt.format("%d %B %Y").to_string()  // show dd MON YYY format, if years are different
    }
}