~lthms/keyr

09475b128b66c78c3f97d6d8c01d9929def97b96 — Thomas Letan 2 years ago a4200c0
all: Apply rustfmt globally
A hooks/pre-commit.sh => hooks/pre-commit.sh +3 -0
@@ 0,0 1,3 @@
#!/bin/bash

cargo +nightly fmt --all -- --check

M keyr-agent/cli.rs => keyr-agent/cli.rs +8 -9
@@ 17,7 17,7 @@
 * along with keyr.  If not, see <https://www.gnu.org/licenses/>.
 */

use clap::{App, SubCommand, ArgGroup, ArgMatches};
use clap::{App, ArgGroup, ArgMatches, SubCommand};

pub enum Output<'a> {
    Json,


@@ 31,27 31,26 @@ pub fn get_app() -> App<'static, 'static> {
        .about("Synchronize your keystrokes locally and remotely")
        .subcommand(
            SubCommand::with_name("stage")
                .about("Fetch the current counter of keyrd and stage it")
                .about("Fetch the current counter of keyrd and stage it"),
        )
        .subcommand(
            SubCommand::with_name("commit")
                .about("Push staging keystrokes to a hub")
                .about("Push staging keystrokes to a hub"),
        )
        .subcommand(
            SubCommand::with_name("revert")
                .about("Retreive keystrokes from a hub")
                .about("Retreive keystrokes from a hub"),
        )
        .subcommand(
            SubCommand::with_name("format")
                .about("Format your keystrokes statistics")
                .args_from_usage(
                    "--template [string] 'A template to output the result'
                     --json 'Output the json as computed'"
                     --json 'Output the json as computed'",
                )
                .group(
                    ArgGroup::with_name("output")
                        .args(&["template", "json"])
                )
                    ArgGroup::with_name("output").args(&["template", "json"]),
                ),
        )
}



@@ 59,7 58,7 @@ impl<'a> Output<'a> {
    pub fn from_matches(matches : &'a ArgMatches<'static>) -> Self {
        match matches.value_of("template") {
            Some(tmp) => Output::Template(tmp),
            _         => Output::Json,
            _ => Output::Json,
        }
    }
}

M keyr-agent/commit.rs => keyr-agent/commit.rs +11 -15
@@ 18,12 18,12 @@
 */

use anyhow::Result;
use chrono::{Utc, TimeZone, Local};
use chrono::{Local, TimeZone, Utc};
use reqwest::blocking::Client;

use keyr_agentstorage as kas;
use kas::SqliteConnection;
use keyr_types::{Summary, SynchronizeRequest, KeystrokesStats};
use keyr_agentstorage as kas;
use keyr_types::{KeystrokesStats, Summary, SynchronizeRequest};

use crate::config::HubConfig;



@@ 31,20 31,19 @@ fn commit_inner(
    conn : &SqliteConnection,
    url : &str,
    token : &str,
    sa : KeystrokesStats
    sa : KeystrokesStats,
) -> Result<()> {
    let client = Client::new();

    let today = Local::today()
        .and_hms(0, 0, 0)
        .naive_utc();
    let today = Local::today().and_hms(0, 0, 0).naive_utc();

    let req = SynchronizeRequest {
        staging_area : sa,
        today : today.timestamp()
        today : today.timestamp(),
    };

    let resp = client.post(&format!("{}/commit", url))
    let resp = client
        .post(&format!("{}/commit", url))
        .json(&req)
        .header("Keyr-Token", token)
        .send()?;


@@ 67,12 66,9 @@ fn commit_inner(
}

pub fn run(conn : &SqliteConnection, hub : &HubConfig) -> Result<()> {
    kas::commit(&conn, |sa| commit_inner(
        &conn,
        &hub.hub_url,
        &hub.api_token,
        sa
    ))?;
    kas::commit(&conn, |sa| {
        commit_inner(&conn, &hub.hub_url, &hub.api_token, sa)
    })?;

    Ok(())
}

M keyr-agent/config.rs => keyr-agent/config.rs +7 -11
@@ 18,8 18,8 @@
 */

use anyhow::Result;
use std::path::{Path, PathBuf};
use serde::Deserialize;
use std::path::{Path, PathBuf};

#[derive(Debug, Deserialize, Clone)]
pub struct LocalConfig {


@@ 58,14 58,13 @@ impl AgentConfig {
    }

    pub fn from_file(path : &Path) -> Result<AgentConfig> {
        let mut res : AgentConfig = toml::from_str(
            &std::fs::read_to_string(path)?
        )?;
        let mut res : AgentConfig =
            toml::from_str(&std::fs::read_to_string(path)?)?;

        if let Some(ref local) = res.local {
            if local.database_path.is_relative() {
                res.local = Some(LocalConfig {
                    database_path : path.join(local.database_path.clone())
                    database_path : path.join(local.database_path.clone()),
                })
            }
        }


@@ 75,17 74,15 @@ impl AgentConfig {

    pub fn local_config(&self) -> Result<LocalConfig> {
        match &self.local {
            Some(local) => {
                Ok(local.clone())
            },
            Some(local) => Ok(local.clone()),
            None => {
                let xdg_dirs = xdg::BaseDirectories::with_prefix("keyr")?;
                let path = xdg_dirs.place_config_file("localstorage.sqlite")?;

                Ok(LocalConfig {
                    database_path : path.to_owned()
                    database_path : path.to_owned(),
                })
            },
            }
        }
    }



@@ 94,6 91,5 @@ impl AgentConfig {
            Some(hub) => Ok(hub.clone()),
            None => bail!("Missing keyr-hub configuration."),
        }

    }
}

M keyr-agent/format.rs => keyr-agent/format.rs +9 -9
@@ 1,31 1,31 @@
use anyhow::Result;
use num_format::{SystemLocale, ToFormattedString};
use serde_json::Value;
use tinytemplate::TinyTemplate;
use num_format::{ToFormattedString, SystemLocale};

use keyr_agentstorage as kas;
use kas::SqliteConnection;
use keyr_agentstorage as kas;

use crate::cli::Output;

fn num_format_formatter(
    val : &Value,
    output : &mut String
    output : &mut String,
) -> tinytemplate::error::Result<()> {
    match val {
        Value::Number(x) if x.is_i64() => {
            output.push_str(
                // FIXME
                &x.as_i64().unwrap()
                    .to_formatted_string(&SystemLocale::default().unwrap())
                &x.as_i64()
                    .unwrap()
                    .to_formatted_string(&SystemLocale::default().unwrap()),
            );
            Ok(())
        },
        }
        _ => Err(tinytemplate::error::Error::GenericError {
            msg : "`num_format' is for integers only".into(),
        })
        }),
    }

}

pub fn run(conn : &SqliteConnection, output : &Output) -> Result<()> {


@@ 42,7 42,7 @@ pub fn run(conn : &SqliteConnection, output : &Output) -> Result<()> {
            tt.add_formatter("num_format", num_format_formatter);

            println!("{}", tt.render("fmt", &res)?);
        },
        }
    }

    Ok(())

M keyr-agent/main.rs => keyr-agent/main.rs +7 -5
@@ 17,19 17,21 @@
 * along with keyr.  If not, see <https://www.gnu.org/licenses/>.
 */

#[macro_use] extern crate serde_json;
#[macro_use] extern crate anyhow;
#[macro_use]
extern crate serde_json;
#[macro_use]
extern crate anyhow;

use anyhow::Result;

use keyr_agentstorage as kas;

pub mod cli;
pub mod config;
pub mod stage;
pub mod commit;
pub mod revert;
pub mod config;
pub mod format;
pub mod revert;
pub mod stage;

use crate::cli::Output;
use crate::config::AgentConfig;

M keyr-agent/revert.rs => keyr-agent/revert.rs +12 -5
@@ 18,11 18,11 @@
 */

use anyhow::Result;
use chrono::{Utc, TimeZone, Timelike};
use chrono::{TimeZone, Timelike, Utc};
use reqwest::blocking::Client;

use keyr_agentstorage as kas;
use kas::SqliteConnection;
use keyr_agentstorage as kas;
use keyr_types::KeystrokesStats;

use crate::config::HubConfig;


@@ 31,7 31,8 @@ pub fn run(conn : &SqliteConnection, hub : &HubConfig) -> Result<()> {
    let client = Client::new();

    kas::transaction_retry(&conn, &|| {
        let resp = client.post(&format!("{}/revert/initiate", &hub.hub_url))
        let resp = client
            .post(&format!("{}/revert/initiate", &hub.hub_url))
            .header("Keyr-Token", &hub.api_token)
            .send()?;



@@ 40,12 41,18 @@ pub fn run(conn : &SqliteConnection, hub : &HubConfig) -> Result<()> {

            for (t, v) in resp {
                let d = Utc.timestamp(t, 0);
                kas::upsert_hour_count_in_transaction(conn, d.date(), d.hour(), v)?;
                kas::upsert_hour_count_in_transaction(
                    conn,
                    d.date(),
                    d.hour(),
                    v,
                )?;
            }

            kas::drop_summary(&conn)?;

            let resp = client.post(&format!("{}/revert/terminate", &hub.hub_url))
            let resp = client
                .post(&format!("{}/revert/terminate", &hub.hub_url))
                .header("Keyr-Token", &hub.api_token)
                .send()?;


M keyr-agent/stage.rs => keyr-agent/stage.rs +1 -1
@@ 22,8 22,8 @@ use std::os::unix::net::UnixStream;

use anyhow::Result;

use keyr_agentstorage as kas;
use kas::SqliteConnection;
use keyr_agentstorage as kas;

fn keyrd_fetch() -> Result<u32> {
    let mut stream = UnixStream::connect("/tmp/keyrd.socket")?;

M keyr-agentstorage/lib.rs => keyr-agentstorage/lib.rs +52 -51
@@ 17,22 17,24 @@
 * along with keyr.  If not, see <https://www.gnu.org/licenses/>.
 */

#[macro_use] extern crate diesel;
#[macro_use] extern crate diesel_migrations;
#[macro_use]
extern crate diesel;
#[macro_use]
extern crate diesel_migrations;

use chrono::{Date, DateTime, Local, NaiveDateTime, TimeZone, Timelike, Utc};
use diesel::prelude::*;
use diesel::result::Error;
pub use diesel::sqlite::SqliteConnection;
use diesel_migrations::RunMigrationsError;
use chrono::{DateTime, Utc, Local, Timelike, Date, TimeZone, NaiveDateTime};

use std::fmt::Display;
use std::time::Duration;
use std::collections::HashMap;
use std::fmt::Display;
use std::path::Path;
use std::time::Duration;

mod schema;
mod migrations;
mod schema;

use schema::staging_area as sa;
use schema::summary;


@@ 51,7 53,8 @@ pub fn transaction_retry<A, E, F>(
) -> Result<A, E>
where
    E : From<Error> + Display,
    F : Fn() -> Result<A, E>, {
    F : Fn() -> Result<A, E>,
{
    loop {
        match conn.exclusive_transaction(f) {
            Err(err) => {


@@ 60,17 63,15 @@ where
                    continue;
                }

                break Err(err)
            },
                break Err(err);
            }
            res => break res,
        }
    }
}

pub fn get_today_count(conn : &SqliteConnection) -> Result<u64, Error> {
    let today = Local::today()
        .and_hms(0, 0, 0)
        .naive_utc();
    let today = Local::today().and_hms(0, 0, 0).naive_utc();

    transaction_retry(conn, &|| {
        let staging_count = sa::table


@@ 87,7 88,6 @@ pub fn get_today_count(conn : &SqliteConnection) -> Result<u64, Error> {
            .unwrap_or(0);

        Ok(staging_count as u64 + summary_count as u64)

    })
}



@@ 109,11 109,8 @@ pub fn get_global_count(conn : &SqliteConnection) -> Result<u64, Error> {
    })
}

pub fn drop_summary(
    conn : &SqliteConnection,
) -> Result<(), Error> {
    diesel::delete(summary::table)
        .execute(conn)?;
pub fn drop_summary(conn : &SqliteConnection) -> Result<(), Error> {
    diesel::delete(summary::table).execute(conn)?;

    Ok(())
}


@@ 123,33 120,38 @@ pub fn set_summary_in_transaction(
    oldest : DateTime<Utc>,
    global_count : u64,
    today : DateTime<Utc>,
    today_count : u64
    today_count : u64,
) -> Result<(), Error> {
    diesel::delete(summary::table)
        .execute(conn)?;
    diesel::delete(summary::table).execute(conn)?;

    diesel::insert_into(summary::table)
        .values(vec![
            (summary::since.eq(oldest.naive_utc()),
             summary::count.eq(global_count as i64)),
        ])
        .values(vec![(
            summary::since.eq(oldest.naive_utc()),
            summary::count.eq(global_count as i64),
        )])
        .execute(conn)?;

    diesel::insert_into(summary::table)
        .values(vec![
            (summary::since.eq(today.naive_utc()),
             summary::count.eq(today_count as i64)),
        ])
        .values(vec![(
            summary::since.eq(today.naive_utc()),
            summary::count.eq(today_count as i64),
        )])
        .execute(conn)?;

    Ok(())
}

pub fn upsert_current_hour_count(conn : &SqliteConnection, count : u32) -> Result<u32, Error> {
pub fn upsert_current_hour_count(
    conn : &SqliteConnection,
    count : u32,
) -> Result<u32, Error> {
    let now = Utc::now()
        .with_nanosecond(0).unwrap()
        .with_second(0).unwrap()
        .with_minute(0).unwrap();
        .with_nanosecond(0)
        .unwrap()
        .with_second(0)
        .unwrap()
        .with_minute(0)
        .unwrap();

    upsert_hour_count(conn, now.date(), now.hour(), count)
}


@@ 160,7 162,9 @@ pub fn upsert_hour_count_in_transaction<Tz>(
    hour : u32,
    count : u32,
) -> Result<u32, Error>
where Tz : TimeZone {
where
    Tz : TimeZone,
{
    let now = dt.and_hms(hour, 0, 0).naive_utc();

    if count != 0 {


@@ 180,10 184,10 @@ where Tz : TimeZone {
            Ok(new_count as u32)
        } else {
            diesel::insert_into(sa::table)
                .values(vec![
                    (sa::timestamp.eq(now),
                     sa::count.eq(count as i32)),
                ])
                .values(vec![(
                    sa::timestamp.eq(now),
                    sa::count.eq(count as i32),
                )])
                .execute(conn)?;

            Ok(count)


@@ 199,7 203,9 @@ pub fn upsert_hour_count<Tz>(
    hour : u32,
    count : u32,
) -> Result<u32, Error>
where Tz : TimeZone {
where
    Tz : TimeZone,
{
    transaction_retry(conn, &|| {
        upsert_hour_count_in_transaction(conn, dt.clone(), hour, count)
    })


@@ 207,11 213,9 @@ where Tz : TimeZone {

pub fn migrate(conn : &SqliteConnection) -> Result<(), Error> {
    transaction_retry(conn, &|| {
        migrations::run(conn).map_err(|err| {
            match err {
                RunMigrationsError::QueryError(err) => err,
                _ => panic!("FIXME")
            }
        migrations::run(conn).map_err(|err| match err {
            RunMigrationsError::QueryError(err) => err,
            _ => panic!("FIXME"),
        })
    })
}


@@ 240,12 244,10 @@ fn drop_staging_area_in_transaction(
    Ok(())
}

pub fn commit<A, E, K>(
    conn : &SqliteConnection,
    k : K
) -> Result<A, Error>
pub fn commit<A, E, K>(conn : &SqliteConnection, k : K) -> Result<A, Error>
where
    K : Fn(KeystrokesStats) -> Result<A, E> {
    K : Fn(KeystrokesStats) -> Result<A, E>,
{
    transaction_retry(conn, &|| {
        let sa = get_staging_area_in_transaction(conn)?;



@@ 253,11 255,10 @@ where
            Ok(res) => {
                drop_staging_area_in_transaction(conn)?;
                Ok(res)
            },
            }
            Err(_) => {
                panic!() // FIXME
            }

        }
    })
}

M keyr-agentstorage/schema.rs => keyr-agentstorage/schema.rs +1 -4
@@ 12,7 12,4 @@ table! {
    }
}

allow_tables_to_appear_in_same_query!(
    staging_area,
    summary,
);
allow_tables_to_appear_in_same_query!(staging_area, summary,);

M keyr-hub/auth.rs => keyr-hub/auth.rs +4 -4
@@ 17,12 17,12 @@
 * along with keyr.  If not, see <https://www.gnu.org/licenses/>.
 */

use actix_web::{FromRequest, HttpRequest};
use actix_web::dev::Payload;
use futures::future::{Ready, ok, err};
use actix_web::{FromRequest, HttpRequest};
use futures::future::{err, ok, Ready};

use keyr_hubstorage as kbs;
use kbs::users::Token;
use keyr_hubstorage as kbs;

use crate::error::KeyrHubError;



@@ 39,7 39,7 @@ impl FromRequest for TokenHeader {
    type Error = KeyrHubError;
    type Future = Ready<Result<TokenHeader, KeyrHubError>>;

    fn from_request(req: &HttpRequest, _pl: &mut Payload) -> Self::Future {
    fn from_request(req : &HttpRequest, _pl : &mut Payload) -> Self::Future {
        if let Some(token) = req.headers().get("keyr-token") {
            let token = token.to_str().unwrap_or("").to_owned();


M keyr-hub/cli.rs => keyr-hub/cli.rs +1 -1
@@ 29,6 29,6 @@ pub fn get_app() -> App<'static, 'static> {
                .help("A path to a TOML file")
                .long("config-file")
                .value_name("FILE")
                .required(true)
                .required(true),
        )
}

M keyr-hub/config.rs => keyr-hub/config.rs +4 -6
@@ 17,9 17,9 @@
 * along with keyr.  If not, see <https://www.gnu.org/licenses/>.
 */

use anyhow::Result;
use serde::Deserialize;
use std::path::Path;
use anyhow::Result;

#[derive(Debug, Deserialize, Clone)]
pub struct DatabaseConfig {


@@ 40,12 40,9 @@ pub struct HubConfig {
    pub database : DatabaseConfig,
}


impl HubConfig {
    pub fn from_file(path : &Path) -> Result<HubConfig> {
        let res : HubConfig = toml::from_str(
            &std::fs::read_to_string(path)?
        )?;
        let res : HubConfig = toml::from_str(&std::fs::read_to_string(path)?)?;

        Ok(res)
    }


@@ 54,7 51,8 @@ impl HubConfig {
        format!(
            "postgres://{}:{}@{}",
            self.database.user,
            self.database.password
            self.database
                .password
                .as_ref()
                .map(|x| x.clone())
                .unwrap_or("".to_owned()),

M keyr-hub/database.rs => keyr-hub/database.rs +1 -1
@@ 17,8 17,8 @@
 * along with keyr.  If not, see <https://www.gnu.org/licenses/>.
 */

use diesel::PgConnection;
use diesel::r2d2::{ConnectionManager, Pool};
use diesel::PgConnection;

use crate::error::Result;


M keyr-hub/error.rs => keyr-hub/error.rs +12 -10
@@ 21,8 21,8 @@ use actix_web::error::ResponseError;
use actix_web::http::StatusCode;
use thiserror::Error;

use keyr_hubstorage as kbs;
use kbs::error::KeyrHubstorageError;
use keyr_hubstorage as kbs;

#[derive(Error, Debug)]
pub enum KeyrHubError {


@@ 35,7 35,7 @@ pub enum KeyrHubError {
    #[error(transparent)]
    IO(#[from] std::io::Error),
    #[error("The requested data are not public")]
    PrivateData
    PrivateData,
}

impl From<diesel::result::Error> for KeyrHubError {


@@ 49,15 49,17 @@ pub type Result<R> = std::result::Result<R, KeyrHubError>;
impl ResponseError for KeyrHubError {
    fn status_code(&self) -> StatusCode {
        match self {
            KeyrHubError::PrivateData =>
                StatusCode::UNAUTHORIZED,
            KeyrHubError::PrivateData => StatusCode::UNAUTHORIZED,
            KeyrHubError::MissingKeyrTokenHeader => StatusCode::UNAUTHORIZED,
            KeyrHubError::Storage(KeyrHubstorageError::InvalidToken) =>
                StatusCode::UNAUTHORIZED,
            KeyrHubError::Storage(KeyrHubstorageError::UnknownUser) =>
                StatusCode::BAD_REQUEST,
            KeyrHubError::Storage(KeyrHubstorageError::AlreadyUsedNickname(_)) =>
                StatusCode::BAD_REQUEST,
            KeyrHubError::Storage(KeyrHubstorageError::InvalidToken) => {
                StatusCode::UNAUTHORIZED
            }
            KeyrHubError::Storage(KeyrHubstorageError::UnknownUser) => {
                StatusCode::BAD_REQUEST
            }
            KeyrHubError::Storage(
                KeyrHubstorageError::AlreadyUsedNickname(_),
            ) => StatusCode::BAD_REQUEST,
            KeyrHubError::Storage(_) => StatusCode::INTERNAL_SERVER_ERROR,
            KeyrHubError::Pool(_) => StatusCode::INTERNAL_SERVER_ERROR,
            KeyrHubError::IO(_) => StatusCode::INTERNAL_SERVER_ERROR,

M keyr-hub/main.rs => keyr-hub/main.rs +25 -25
@@ 17,46 17,47 @@
 * along with keyr.  If not, see <https://www.gnu.org/licenses/>.
 */

pub mod error;
pub mod database;
pub mod auth;
pub mod config;
pub mod cli;
pub mod config;
pub mod database;
pub mod error;

use diesel::prelude::*;

use actix_web::{App, HttpServer, get, post};
use actix_web::web::{Path, Json, Data};
use chrono::{Utc, TimeZone};
use actix_web::web::{Data, Json, Path};
use actix_web::{get, post, App, HttpServer};
use chrono::{TimeZone, Utc};

use std::path::PathBuf;

use keyr_hubstorage as khs;
use khs::users;

use keyr_types::{SynchronizeRequest, KeystrokesStats, Summary};
use keyr_types::{KeystrokesStats, Summary, SynchronizeRequest};

use crate::error::KeyrHubError;
use crate::database::{PgPool, create_pool};
use crate::auth::TokenHeader;
use crate::config::HubConfig;
use crate::database::{create_pool, PgPool};
use crate::error::KeyrHubError;

#[post("/commit")]
async fn commit(
    pool : Data<PgPool>,
    tok : TokenHeader,
    request : Json<SynchronizeRequest>
    request : Json<SynchronizeRequest>,
) -> Result<Json<Summary>, KeyrHubError> {
    let conn = pool.into_inner().get()?;

    let mid = users::identify_user_by_token(&conn, tok.as_token())?;
    let today = Utc.timestamp(request.today, 0);

    Ok(
        Json(
            khs::stats::commit(&conn, mid, today, &request.staging_area)?
        )
    )
    Ok(Json(khs::stats::commit(
        &conn,
        mid,
        today,
        &request.staging_area,
    )?))
}

#[post("/revert/initiate")]


@@ 131,26 132,25 @@ async fn run() -> anyhow::Result<()> {

    khs::migrations::run(&pool.get()?)?;

    HttpServer::new(
        move || App::new()
    HttpServer::new(move || {
        App::new()
            .data(pool.clone())
            .service(commit)
            .service(revert_initiate)
            .service(revert_terminate)
            .service(revert_cancel)
            .service(view_stats)
    )
        .bind(&format!("{}:{}", conf.http.url, conf.http.port))?
        .run()
        .await?;
    })
    .bind(&format!("{}:{}", conf.http.url, conf.http.port))?
    .run()
    .await?;

    Ok(())
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    run().await
        .map_err(
            |err| std::io::Error::new(std::io::ErrorKind::Other, err.to_string())
        )
    run().await.map_err(|err| {
        std::io::Error::new(std::io::ErrorKind::Other, err.to_string())
    })
}

M keyr-hubstorage/error.rs => keyr-hubstorage/error.rs +1 -1
@@ 1,5 1,5 @@
use thiserror::Error;
use diesel_migrations as dm;
use thiserror::Error;

#[derive(Error, Debug)]
pub enum KeyrHubstorageError {

M keyr-hubstorage/lib.rs => keyr-hubstorage/lib.rs +6 -4
@@ 17,11 17,13 @@
 * along with keyr.  If not, see <https://www.gnu.org/licenses/>.
 */

#[macro_use] extern crate diesel_migrations;
#[macro_use] extern crate diesel;
#[macro_use]
extern crate diesel_migrations;
#[macro_use]
extern crate diesel;

pub mod error;
pub mod migrations;
pub mod schema;
pub mod users;
pub mod stats;
pub mod migrations;
pub mod users;

M keyr-hubstorage/migrations.rs => keyr-hubstorage/migrations.rs +5 -5
@@ 1,12 1,12 @@
embed_migrations!();

use diesel::prelude::*;
use diesel::pg::Pg;
use diesel::prelude::*;

pub fn run<Conn>(
    conn : &Conn
) -> crate::error::Result<()>
where Conn : Connection<Backend = Pg> {
pub fn run<Conn>(conn : &Conn) -> crate::error::Result<()>
where
    Conn : Connection<Backend = Pg>,
{
    embedded_migrations::run(conn)?;

    Ok(())

M keyr-hubstorage/schema.rs => keyr-hubstorage/schema.rs +1 -5
@@ 27,8 27,4 @@ table! {
joinable!(statistics -> users (user_id));
joinable!(tokens -> users (user_id));

allow_tables_to_appear_in_same_query!(
    statistics,
    tokens,
    users,
);
allow_tables_to_appear_in_same_query!(statistics, tokens, users,);

M keyr-hubstorage/stats.rs => keyr-hubstorage/stats.rs +49 -33
@@ 1,14 1,14 @@
use chrono::{Utc, NaiveDateTime, DateTime, Timelike, TimeZone};
use diesel::prelude::*;
use chrono::{DateTime, NaiveDateTime, TimeZone, Timelike, Utc};
use diesel::pg::Pg;
use diesel::prelude::*;

use std::collections::HashMap;

use keyr_types::{Summary, KeystrokesStats};
use keyr_types::{KeystrokesStats, Summary};

use crate::users::{MaybeUserId, UserId};
use crate::schema::statistics as stats;
use crate::error::{KeyrHubstorageError, Result};
use crate::schema::statistics as stats;
use crate::users::{MaybeUserId, UserId};

pub fn upsert_keystrokes_count<Conn>(
    conn : &Conn,


@@ 16,12 16,13 @@ pub fn upsert_keystrokes_count<Conn>(
    date : &DateTime<Utc>,
    count : i32,
) -> Result<()>
where Conn : Connection<Backend = Pg> {
where
    Conn : Connection<Backend = Pg>,
{
    conn.transaction(|| {
        let id = mid.validate(conn)?;
        upsert_keystrokes_count_in_transaction(conn, id, &date, count)
    })

}

pub fn upsert_keystrokes_count_in_transaction<Conn>(


@@ 30,15 31,20 @@ pub fn upsert_keystrokes_count_in_transaction<Conn>(
    date : &DateTime<Utc>,
    count : i32,
) -> Result<()>
where Conn : Connection<Backend = Pg> {
where
    Conn : Connection<Backend = Pg>,
{
    if crate::users::is_frozen_in_transaction(conn, id)? {
        return Err(KeyrHubstorageError::FrozenUser);
    }

    let date = date
        .with_nanosecond(0).unwrap()
        .with_second(0).unwrap()
        .with_minute(0).unwrap()
        .with_nanosecond(0)
        .unwrap()
        .with_second(0)
        .unwrap()
        .with_minute(0)
        .unwrap()
        .naive_utc();

    let prev = stats::table


@@ 53,14 59,14 @@ where Conn : Connection<Backend = Pg> {
            diesel::update(stats::table.find(id))
                .set(stats::count.eq(prev_count + count))
                .execute(conn)?;
        },
        }
        None => {
            diesel::insert_into(stats::table)
                .values(vec![
                    (stats::timestamp.eq(&date),
                     stats::count.eq(count),
                     stats::user_id.eq(id.0))
                ])
                .values(vec![(
                    stats::timestamp.eq(&date),
                    stats::count.eq(count),
                    stats::user_id.eq(id.0),
                )])
                .execute(conn)?;
        }
    }


@@ 74,7 80,9 @@ pub fn commit<Conn>(
    today : DateTime<Utc>,
    sa : &KeystrokesStats,
) -> Result<Summary>
where Conn : Connection<Backend = Pg> {
where
    Conn : Connection<Backend = Pg>,
{
    conn.transaction(|| {
        let id = id.validate(conn)?;



@@ 95,7 103,9 @@ pub fn get_summary_in_transaction<Conn>(
    id : UserId,
    today : DateTime<Utc>,
) -> Result<Summary>
where Conn : Connection<Backend = Pg> {
where
    Conn : Connection<Backend = Pg>,
{
    let oldest_entry = stats::table
        .select(stats::timestamp)
        .filter(stats::user_id.eq(id.0))


@@ 127,7 137,9 @@ pub fn get_keystrokes_stats_in_transaction<Conn>(
    conn : &Conn,
    id : UserId,
) -> Result<KeystrokesStats>
where Conn : Connection<Backend = Pg> {
where
    Conn : Connection<Backend = Pg>,
{
    let datas = stats::table
        .select((stats::timestamp, stats::count))
        .filter(stats::user_id.eq(id.0))


@@ 144,9 156,11 @@ where Conn : Connection<Backend = Pg> {

pub fn initiate_revert_in_transaction<Conn>(
    conn : &Conn,
    id : UserId
    id : UserId,
) -> Result<KeystrokesStats>
where Conn : Connection<Backend = Pg> {
where
    Conn : Connection<Backend = Pg>,
{
    crate::users::freeze_user_in_transaction(conn, id)?;

    get_keystrokes_stats_in_transaction(conn, id)


@@ 154,9 168,11 @@ where Conn : Connection<Backend = Pg> {

pub fn initiate_revert<Conn>(
    conn : &Conn,
    id : MaybeUserId
    id : MaybeUserId,
) -> Result<KeystrokesStats>
where Conn : Connection<Backend = Pg> {
where
    Conn : Connection<Backend = Pg>,
{
    conn.transaction(|| {
        let id = id.validate(conn)?;
        let res = initiate_revert_in_transaction(conn, id)?;


@@ 167,11 183,12 @@ where Conn : Connection<Backend = Pg> {

pub fn terminate_revert_in_transaction<Conn>(
    conn : &Conn,
    id : UserId
    id : UserId,
) -> Result<()>
where Conn : Connection<Backend = Pg> {
    diesel::delete(stats::table
                   .filter(stats::user_id.eq(id.0)))
where
    Conn : Connection<Backend = Pg>,
{
    diesel::delete(stats::table.filter(stats::user_id.eq(id.0)))
        .execute(conn)?;

    crate::users::unfreeze_user_in_transaction(conn, id)?;


@@ 179,11 196,10 @@ where Conn : Connection<Backend = Pg> {
    Ok(())
}

pub fn terminate_revert<Conn>(
    conn : &Conn,
    id : MaybeUserId
) -> Result<()>
where Conn : Connection<Backend = Pg> {
pub fn terminate_revert<Conn>(conn : &Conn, id : MaybeUserId) -> Result<()>
where
    Conn : Connection<Backend = Pg>,
{
    conn.transaction(|| {
        let id = id.validate(conn)?;
        terminate_revert_in_transaction(conn, id)

M keyr-hubstorage/users.rs => keyr-hubstorage/users.rs +65 -68
@@ 17,12 17,12 @@
 * along with keyr.  If not, see <https://www.gnu.org/licenses/>.
 */

use diesel::prelude::*;
use diesel::pg::Pg;
use diesel::prelude::*;
use uuid::Uuid;

use crate::error::{KeyrHubstorageError, Result};
use crate::schema::{users, tokens};
use crate::schema::{tokens, users};

#[derive(Copy, Clone)]
pub struct UserId(pub i32);


@@ 41,11 41,10 @@ impl Into<UserId> for i32 {

impl MaybeUserId {
    // Needs to be run in a transaction. Ensure the user exists.
    pub fn validate<Conn>(
        &self,
        conn : &Conn,
    ) -> Result<UserId>
    where Conn : Connection<Backend = Pg> {
    pub fn validate<Conn>(&self, conn : &Conn) -> Result<UserId>
    where
        Conn : Connection<Backend = Pg>,
    {
        let id = users::table
            .select(users::id)
            .filter(users::id.eq(self.0))


@@ 59,14 58,12 @@ impl MaybeUserId {

// Create a new user with a given name. Check whether or not the name is
// available before.
pub fn create_user<Conn>(
    conn : &Conn,
    name : String
) -> Result<MaybeUserId>
where Conn : Connection<Backend = Pg> {
pub fn create_user<Conn>(conn : &Conn, name : String) -> Result<MaybeUserId>
where
    Conn : Connection<Backend = Pg>,
{
    conn.transaction(|| {
        create_user_in_transaction(conn, name)
            .map(|x| MaybeUserId(x.0))
        create_user_in_transaction(conn, name).map(|x| MaybeUserId(x.0))
    })
}



@@ 74,9 71,11 @@ where Conn : Connection<Backend = Pg> {
// available before. This needs to be called from within a transaction.
pub fn create_user_in_transaction<Conn>(
    conn : &Conn,
    name : String
    name : String,
) -> Result<UserId>
where Conn : Connection<Backend = Pg> {
where
    Conn : Connection<Backend = Pg>,
{
    let prev = users::table
        .select(users::id)
        .filter(users::name.eq(&name))


@@ 84,9 83,7 @@ where Conn : Connection<Backend = Pg> {

    if prev.len() == 0 {
        let id = diesel::insert_into(users::table)
            .values(vec![
                users::name.eq(&name)
            ])
            .values(vec![users::name.eq(&name)])
            .returning(users::id)
            .get_result::<i32>(conn)?;



@@ 98,11 95,10 @@ where Conn : Connection<Backend = Pg> {

// Generate a token for a user identified by a potential id. Returns None if the
// user does not exists.
pub fn generate_token<Conn>(
    conn : &Conn,
    user : MaybeUserId,
) -> Result<Token>
where Conn : Connection<Backend = Pg> {
pub fn generate_token<Conn>(conn : &Conn, user : MaybeUserId) -> Result<Token>
where
    Conn : Connection<Backend = Pg>,
{
    conn.transaction(|| {
        let id = user.validate(conn)?;



@@ 116,14 112,13 @@ pub fn generate_token_in_transaction<Conn>(
    conn : &Conn,
    id : UserId,
) -> Result<Token>
where Conn : Connection<Backend = Pg> {
where
    Conn : Connection<Backend = Pg>,
{
    let token = Uuid::new_v4().to_simple().to_string();

    diesel::insert_into(tokens::table)
        .values(vec![
            (tokens::user_id.eq(id.0),
             tokens::token.eq(&token))
        ])
        .values(vec![(tokens::user_id.eq(id.0), tokens::token.eq(&token))])
        .execute(conn)?;

    Ok(Token(token))


@@ 135,7 130,9 @@ pub fn identify_user_by_token_in_transaction<Conn>(
    conn : &Conn,
    token : &Token,
) -> Result<UserId>
where Conn : Connection<Backend = Pg> {
where
    Conn : Connection<Backend = Pg>,
{
    let id = tokens::table
        .select(tokens::user_id)
        .filter(tokens::token.eq(&token.0))


@@ 152,12 149,12 @@ pub fn identify_user_by_token<Conn>(
    conn : &Conn,
    token : &Token,
) -> Result<MaybeUserId>
where Conn : Connection<Backend = Pg> {
where
    Conn : Connection<Backend = Pg>,
{
    conn.transaction(|| {
        identify_user_by_token_in_transaction(
            conn,
            token
        ).map(|x| MaybeUserId(x.0))
        identify_user_by_token_in_transaction(conn, token)
            .map(|x| MaybeUserId(x.0))
    })
}



@@ 165,7 162,9 @@ pub fn find_by_name_in_transaction<Conn>(
    conn : &Conn,
    name : String,
) -> Result<UserId>
where Conn : Connection<Backend = Pg> {
where
    Conn : Connection<Backend = Pg>,
{
    let id = users::table
        .select(users::id)
        .filter(users::name.eq(name))


@@ 176,11 175,10 @@ where Conn : Connection<Backend = Pg> {
    Ok(UserId(id))
}

pub fn freeze_user_in_transaction<Conn>(
    conn : &Conn,
    id : UserId,
) -> Result<()>
where Conn : Connection<Backend = Pg> {
pub fn freeze_user_in_transaction<Conn>(conn : &Conn, id : UserId) -> Result<()>
where
    Conn : Connection<Backend = Pg>,
{
    diesel::update(users::table.find(id.0))
        .set(users::frozen.eq(true))
        .execute(conn)?;


@@ 188,11 186,10 @@ where Conn : Connection<Backend = Pg> {
    Ok(())
}

pub fn is_frozen_in_transaction<Conn>(
    conn : &Conn,
    id : UserId,
) -> Result<bool>
where Conn : Connection<Backend = Pg> {
pub fn is_frozen_in_transaction<Conn>(conn : &Conn, id : UserId) -> Result<bool>
where
    Conn : Connection<Backend = Pg>,
{
    let res = users::table
        .filter(users::id.eq(id.0))
        .select(users::frozen)


@@ 201,11 198,10 @@ where Conn : Connection<Backend = Pg> {
    Ok(res)
}

pub fn is_frozen<Conn>(
    conn : &Conn,
    id : MaybeUserId,
) -> Result<bool>
where Conn : Connection<Backend = Pg> {
pub fn is_frozen<Conn>(conn : &Conn, id : MaybeUserId) -> Result<bool>
where
    Conn : Connection<Backend = Pg>,
{
    conn.transaction(|| {
        let id = id.validate(conn)?;



@@ 217,7 213,9 @@ pub fn is_visible_in_transaction<Conn>(
    conn : &Conn,
    id : UserId,
) -> Result<bool>
where Conn : Connection<Backend = Pg> {
where
    Conn : Connection<Backend = Pg>,
{
    let res = users::table
        .filter(users::id.eq(id.0))
        .select(users::visible)


@@ 226,11 224,10 @@ where Conn : Connection<Backend = Pg> {
    Ok(res)
}

pub fn is_visible<Conn>(
    conn : &Conn,
    id : MaybeUserId,
) -> Result<bool>
where Conn : Connection<Backend = Pg> {
pub fn is_visible<Conn>(conn : &Conn, id : MaybeUserId) -> Result<bool>
where
    Conn : Connection<Backend = Pg>,
{
    conn.transaction(|| {
        let id = id.validate(conn)?;



@@ 238,11 235,10 @@ where Conn : Connection<Backend = Pg> {
    })
}

pub fn freeze_user<Conn>(
    conn : &Conn,
    mid : MaybeUserId,
) -> Result<()>
where Conn : Connection<Backend = Pg> {
pub fn freeze_user<Conn>(conn : &Conn, mid : MaybeUserId) -> Result<()>
where
    Conn : Connection<Backend = Pg>,
{
    conn.transaction(|| {
        let id = mid.validate(conn)?;



@@ 254,7 250,9 @@ pub fn unfreeze_user_in_transaction<Conn>(
    conn : &Conn,
    id : UserId,
) -> Result<()>
where Conn : Connection<Backend = Pg> {
where
    Conn : Connection<Backend = Pg>,
{
    diesel::update(users::table.find(id.0))
        .set(users::frozen.eq(false))
        .execute(conn)?;


@@ 262,11 260,10 @@ where Conn : Connection<Backend = Pg> {
    Ok(())
}

pub fn unfreeze_user<Conn>(
    conn : &Conn,
    mid : MaybeUserId,
) -> Result<()>
where Conn : Connection<Backend = Pg> {
pub fn unfreeze_user<Conn>(conn : &Conn, mid : MaybeUserId) -> Result<()>
where
    Conn : Connection<Backend = Pg>,
{
    conn.transaction(|| {
        let id = mid.validate(conn)?;
        unfreeze_user_in_transaction(conn, id)

M keyr-types/lib.rs => keyr-types/lib.rs +2 -1
@@ 1,4 1,5 @@
#[macro_use] extern crate serde_derive;
#[macro_use]
extern crate serde_derive;

use std::collections::HashMap;


A rustfmt.toml => rustfmt.toml +2 -0
@@ 0,0 1,2 @@
space_before_colon = true
max_width = 80
\ No newline at end of file