summaryrefslogtreecommitdiffstats
path: root/src/remote/views.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/remote/views.rs')
-rw-r--r--src/remote/views.rs144
1 files changed, 120 insertions, 24 deletions
diff --git a/src/remote/views.rs b/src/remote/views.rs
index 2af3f369..08dff13e 100644
--- a/src/remote/views.rs
+++ b/src/remote/views.rs
@@ -1,14 +1,22 @@
-use self::diesel::prelude::*;
+use chrono::Utc;
+use rocket::http::uri::Uri;
+use rocket::http::RawStr;
use rocket::http::{ContentType, Status};
+use rocket::request::FromFormValue;
use rocket::request::Request;
use rocket::response;
use rocket::response::{Responder, Response};
use rocket_contrib::databases::diesel;
use rocket_contrib::json::{Json, JsonValue};
-use super::database::AtuinDbConn;
-use super::models::{NewHistory, User};
+use self::diesel::prelude::*;
+
+use crate::api::AddHistoryRequest;
use crate::schema::history;
+use crate::settings::HISTORY_PAGE_SIZE;
+
+use super::database::AtuinDbConn;
+use super::models::{History, NewHistory, User};
#[derive(Debug)]
pub struct ApiResponse {
@@ -46,40 +54,36 @@ pub fn bad_request(_req: &Request) -> ApiResponse {
}
}
-#[derive(Deserialize)]
-pub struct AddHistory {
- id: String,
- timestamp: i64,
- data: String,
- mac: String,
-}
-
#[post("/history", data = "<add_history>")]
#[allow(
clippy::clippy::cast_sign_loss,
clippy::cast_possible_truncation,
clippy::clippy::needless_pass_by_value
)]
-pub fn add_history(conn: AtuinDbConn, user: User, add_history: Json<AddHistory>) -> ApiResponse {
- let secs: i64 = add_history.timestamp / 1_000_000_000;
- let nanosecs: u32 = (add_history.timestamp - (secs * 1_000_000_000)) as u32;
- let datetime = chrono::NaiveDateTime::from_timestamp(secs, nanosecs);
-
- let new_history = NewHistory {
- client_id: add_history.id.as_str(),
- user_id: user.id,
- mac: add_history.mac.as_str(),
- timestamp: datetime,
- data: add_history.data.as_str(),
- };
+pub fn add_history(
+ conn: AtuinDbConn,
+ user: User,
+ add_history: Json<Vec<AddHistoryRequest>>,
+) -> ApiResponse {
+ let new_history: Vec<NewHistory> = add_history
+ .iter()
+ .map(|h| NewHistory {
+ client_id: h.id.as_str(),
+ hostname: h.hostname.to_string(),
+ user_id: user.id,
+ timestamp: h.timestamp.naive_utc(),
+ data: h.data.as_str(),
+ })
+ .collect();
match diesel::insert_into(history::table)
.values(&new_history)
+ .on_conflict_do_nothing()
.execute(&*conn)
{
Ok(_) => ApiResponse {
status: Status::Ok,
- json: json!({"status": "ok", "message": "history added", "id": new_history.client_id}),
+ json: json!({"status": "ok", "message": "history added"}),
},
Err(_) => ApiResponse {
status: Status::BadRequest,
@@ -87,3 +91,95 @@ pub fn add_history(conn: AtuinDbConn, user: User, add_history: Json<AddHistory>)
},
}
}
+
+#[get("/sync/count")]
+#[allow(clippy::wildcard_imports, clippy::needless_pass_by_value)]
+pub fn sync_count(conn: AtuinDbConn, user: User) -> ApiResponse {
+ use crate::schema::history::dsl::*;
+
+ // we need to return the number of history items we have for this user
+ // in the future I'd like to use something like a merkel tree to calculate
+ // which day specifically needs syncing
+ let count = history
+ .filter(user_id.eq(user.id))
+ .count()
+ .first::<i64>(&*conn);
+
+ if count.is_err() {
+ error!("failed to count: {}", count.err().unwrap());
+
+ return ApiResponse {
+ json: json!({"message": "internal server error"}),
+ status: Status::InternalServerError,
+ };
+ }
+
+ ApiResponse {
+ status: Status::Ok,
+ json: json!({"count": count.ok()}),
+ }
+}
+
+pub struct UtcDateTime(chrono::DateTime<Utc>);
+
+impl<'v> FromFormValue<'v> for UtcDateTime {
+ type Error = &'v RawStr;
+
+ fn from_form_value(form_value: &'v RawStr) -> Result<UtcDateTime, &'v RawStr> {
+ let time = Uri::percent_decode(form_value.as_bytes()).map_err(|_| form_value)?;
+ let time = time.to_string();
+
+ match chrono::DateTime::parse_from_rfc3339(time.as_str()) {
+ Ok(t) => Ok(UtcDateTime(t.with_timezone(&Utc))),
+ Err(e) => {
+ error!("failed to parse time {}, got: {}", time, e);
+ Err(form_value)
+ }
+ }
+ }
+}
+
+// Request a list of all history items added to the DB after a given timestamp.
+// Provide the current hostname, so that we don't send the client data that
+// originated from them
+#[get("/sync/history?<sync_ts>&<history_ts>&<host>")]
+#[allow(clippy::wildcard_imports, clippy::needless_pass_by_value)]
+pub fn sync_list(
+ conn: AtuinDbConn,
+ user: User,
+ sync_ts: UtcDateTime,
+ history_ts: UtcDateTime,
+ host: String,
+) -> ApiResponse {
+ use crate::schema::history::dsl::*;
+
+ // we need to return the number of history items we have for this user
+ // in the future I'd like to use something like a merkel tree to calculate
+ // which day specifically needs syncing
+ // TODO: Allow for configuring the page size, both from params, and setting
+ // the max in config. 100 is fine for now.
+ let h = history
+ .filter(user_id.eq(user.id))
+ .filter(hostname.ne(host))
+ .filter(created_at.ge(sync_ts.0.naive_utc()))
+ .filter(timestamp.ge(history_ts.0.naive_utc()))
+ .order(timestamp.asc())
+ .limit(HISTORY_PAGE_SIZE)
+ .load::<History>(&*conn);
+
+ if let Err(e) = h {
+ error!("failed to load history: {}", e);
+
+ return ApiResponse {
+ json: json!({"message": "internal server error"}),
+ status: Status::InternalServerError,
+ };
+ }
+
+ let user_data: Vec<String> = h.unwrap().iter().map(|i| i.data.to_string()).collect();
+
+ ApiResponse {
+ status: Status::Ok,
+ json: json!({ "history": user_data }),
+ }
+}