internal_api.rs 16.31 KiB
use crate::api_model::BulkAction;
use crate::api_model::CreateItem;
use crate::api_model::DeleteEdge;
use crate::api_model::UpdateItem;
use crate::error::Error;
use crate::error::Result;
use crate::sql_converters::borrow_sql_params;
use crate::sql_converters::fields_mapping_to_owned_sql_params;
use crate::sql_converters::json_value_to_sqlite;
use crate::sql_converters::sqlite_row_to_map;
use crate::sql_converters::sqlite_rows_to_json;
use crate::sql_converters::validate_field_name;
use chrono::Utc;
use log::debug;
use log::info;
use r2d2::Pool;
use r2d2::PooledConnection;
use r2d2_sqlite::SqliteConnectionManager;
use rusqlite::ToSql;
use rusqlite::Transaction;
use rusqlite::NO_PARAMS;
use serde_json::value::Value::Object;
use serde_json::Value;
use std::collections::HashMap;
use std::process::Command;
use std::str;
use warp::http::status::StatusCode;
/// Check if item exists by uid
pub fn _check_item_exist(
    conn: &PooledConnection<SqliteConnectionManager>,
    uid: i64,
) -> Result<bool> {
    let sql = format!("SELECT COUNT(*) FROM items WHERE uid = {};", uid);
    let result: i64 = conn.query_row(&sql, NO_PARAMS, |row| row.get(0))?;
    Ok(result != 0)
/// Get project version as seen by Cargo.
pub fn get_project_version() -> &'static str {
    debug!("Returning API version...");
    env!("CARGO_PKG_VERSION")
/// See HTTP_API.md for details
pub fn get_item(sqlite: &Pool<SqliteConnectionManager>, uid: i64) -> Result<Vec<Value>> {
    debug!("Getting item {}", uid);
    let conn = sqlite.get()?;
    let mut stmt = conn.prepare_cached("SELECT * FROM items WHERE uid = :uid")?;
    let rows = stmt.query_named(&[(":uid", &uid)])?;
    let json = sqlite_rows_to_json(rows)?;
    Ok(json)
fn check_item_exists(tx: &Transaction, uid: i64) -> Result<bool> {
    let mut stmt = tx.prepare_cached("SELECT 1 FROM items WHERE uid = :uid")?;
    let mut rows = stmt.query_named(&[(":uid", &uid)])?;
    let result = match rows.next()? {
        None => false,
        Some(row) => {
            let count: isize = row.get(0)?;
            count > 0
    Ok(result)
/// See HTTP_API.md for details
7172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
pub fn get_all_items(sqlite: &Pool<SqliteConnectionManager>) -> Result<Vec<Value>> { debug!("Getting all items"); let conn = sqlite.get()?; let mut stmt = conn.prepare_cached("SELECT * FROM items")?; let rows = stmt.query(NO_PARAMS)?; let json = sqlite_rows_to_json(rows)?; Ok(json) } fn is_array_or_object(value: &Value) -> bool { match value { Value::Array(_) => true, Value::Object(_) => true, _ => false, } } fn write_sql_body(sql: &mut String, keys: &[&String], separator: &str) { let mut first = true; for key in keys { if !first { sql.push_str(separator); } sql.push_str(key); first = false; } } fn execute_sql(tx: &Transaction, sql: &str, fields: &HashMap<String, Value>) -> Result<()> { let mut sql_params = Vec::new(); for (key, value) in fields { sql_params.push((format!(":{}", key), json_value_to_sqlite(value)?)); } let sql_params: Vec<_> = sql_params .iter() .map(|(field, value)| (field.as_str(), value as &dyn ToSql)) .collect(); let mut stmt = tx.prepare_cached(&sql)?; stmt.execute_named(&sql_params).map_err(|err| { let msg = format!( "Database rusqlite error for parameters: {:?}, {}", fields, err ); Error { code: StatusCode::BAD_REQUEST, msg, } })?; Ok(()) } /// Create an item presuming consistency checks were already done fn create_item_tx(tx: &Transaction, fields: HashMap<String, Value>) -> Result<i64> { let mut fields: HashMap<String, Value> = fields .into_iter() .filter(|(k, v)| !is_array_or_object(v) && validate_field_name(k).is_ok()) .collect(); let time_now = Utc::now().timestamp_millis(); if !fields.contains_key("dateCreated") { fields.insert("dateCreated".to_string(), time_now.into()); } if !fields.contains_key("dateModified") { fields.insert("dateModified".to_string(), time_now.into()); } fields.insert("version".to_string(), Value::from(1)); let mut sql = "INSERT INTO items (".to_string(); let keys: Vec<_> = fields.keys().collect(); write_sql_body(&mut sql, &keys, ", "); sql.push_str(") VALUES (:");
141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
write_sql_body(&mut sql, &keys, ", :"); sql.push_str(");"); execute_sql(tx, &sql, &fields)?; Ok(tx.last_insert_rowid()) } /// Update an item presuming all dangerous fields were already removed, and "uid" is present fn update_item_tx(tx: &Transaction, uid: i64, fields: HashMap<String, Value>) -> Result<()> { let mut fields: HashMap<String, Value> = fields .into_iter() .filter(|(k, v)| !is_array_or_object(v) && validate_field_name(k).is_ok()) .collect(); fields.insert("uid".to_string(), uid.into()); fields.remove("_type"); fields.remove("dateCreated"); if !fields.contains_key("dateModified") { let time_now = Utc::now().timestamp_millis(); fields.insert("dateModified".to_string(), time_now.into()); } fields.remove("version"); let mut sql = "UPDATE items SET ".to_string(); let mut after_first = false; for key in fields.keys() { if after_first { sql.push_str(", "); } after_first = true; sql.push_str(key); sql.push_str(" = :"); sql.push_str(key); } sql.push_str(", version = version + 1 "); sql.push_str("WHERE uid = :uid ;"); execute_sql(tx, &sql, &fields) } /// Create an edge presuming consistency checks were already done fn create_edge( tx: &Transaction, _type: String, source: i64, target: i64, mut fields: HashMap<String, Value>, ) -> Result<()> { fields.insert("_type".to_string(), _type.as_str().into()); fields.insert("_source".to_string(), source.into()); fields.insert("_target".to_string(), target.into()); let fields: HashMap<String, Value> = fields .into_iter() .filter(|(k, v)| !is_array_or_object(v) && validate_field_name(k).is_ok()) .collect(); let mut sql = "INSERT INTO edges (".to_string(); let keys: Vec<_> = fields.keys().collect(); write_sql_body(&mut sql, &keys, ", "); sql.push_str(") VALUES (:"); write_sql_body(&mut sql, &keys, ", :"); sql.push_str(");"); if !check_item_exists(tx, source)? { return Err(Error { code: StatusCode::NOT_FOUND, msg: format!( "Failed to create edge {} {}->{} because source uid is not found", _type, source, target ), }); }; if !check_item_exists(tx, target)? { return Err(Error {