-
Vasili Novikov authoredVerified61cbeee5
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 {