diff options
Diffstat (limited to 'crates/turtle/src/atuin_server/handlers')
| -rw-r--r-- | crates/turtle/src/atuin_server/handlers/history.rs | 237 | ||||
| -rw-r--r-- | crates/turtle/src/atuin_server/handlers/mod.rs | 5 | ||||
| -rw-r--r-- | crates/turtle/src/atuin_server/handlers/status.rs | 45 | ||||
| -rw-r--r-- | crates/turtle/src/atuin_server/handlers/user.rs | 32 | ||||
| -rw-r--r-- | crates/turtle/src/atuin_server/handlers/v0/record.rs | 13 | ||||
| -rw-r--r-- | crates/turtle/src/atuin_server/handlers/v0/store.rs | 5 |
6 files changed, 26 insertions, 311 deletions
diff --git a/crates/turtle/src/atuin_server/handlers/history.rs b/crates/turtle/src/atuin_server/handlers/history.rs deleted file mode 100644 index e5057bcb..00000000 --- a/crates/turtle/src/atuin_server/handlers/history.rs +++ /dev/null @@ -1,237 +0,0 @@ -use std::{collections::HashMap, convert::TryFrom}; - -use axum::{ - Json, - extract::{Path, Query, State}, - http::{HeaderMap, StatusCode}, -}; -use metrics::counter; -use time::{Month, UtcOffset}; -use tracing::{debug, error, instrument}; - -use super::{ErrorResponse, ErrorResponseStatus, RespExt}; -use crate::atuin_server::{ - router::{AppState, UserAuth}, - utils::client_version_min, -}; -use crate::atuin_server_database::{ - Database, - calendar::{TimePeriod, TimePeriodInfo}, - models::NewHistory, -}; - -use crate::atuin_common::api::*; - -#[instrument(skip_all, fields(user.id = user.id))] -pub(crate) async fn count<DB: Database>( - UserAuth(user): UserAuth, - state: State<AppState<DB>>, -) -> Result<Json<CountResponse>, ErrorResponseStatus<'static>> { - let db = &state.0.database; - match db.count_history_cached(&user).await { - // By default read out the cached value - Ok(count) => Ok(Json(CountResponse { count })), - - // If that fails, fallback on a full COUNT. Cache is built on a POST - // only - Err(_) => match db.count_history(&user).await { - Ok(count) => Ok(Json(CountResponse { count })), - Err(_) => Err(ErrorResponse::reply("failed to query history count") - .with_status(StatusCode::INTERNAL_SERVER_ERROR)), - }, - } -} - -#[instrument(skip_all, fields(user.id = user.id))] -pub(crate) async fn list<DB: Database>( - req: Query<SyncHistoryRequest>, - UserAuth(user): UserAuth, - headers: HeaderMap, - state: State<AppState<DB>>, -) -> Result<Json<SyncHistoryResponse>, ErrorResponseStatus<'static>> { - let db = &state.0.database; - - let agent = headers - .get("user-agent") - .map_or("", |v| v.to_str().unwrap_or("")); - - let variable_page_size = client_version_min(agent, ">=15.0.0").unwrap_or(false); - - let page_size = if variable_page_size { - state.settings.page_size - } else { - 100 - }; - - if req.sync_ts.unix_timestamp_nanos() < 0 || req.history_ts.unix_timestamp_nanos() < 0 { - error!("client asked for history from < epoch 0"); - counter!("atuin_history_epoch_before_zero").increment(1); - - return Err( - ErrorResponse::reply("asked for history from before epoch 0") - .with_status(StatusCode::BAD_REQUEST), - ); - } - - let history = db - .list_history(&user, req.sync_ts, req.history_ts, &req.host, page_size) - .await; - - if let Err(e) = history { - error!("failed to load history: {}", e); - return Err(ErrorResponse::reply("failed to load history") - .with_status(StatusCode::INTERNAL_SERVER_ERROR)); - } - - let history: Vec<String> = history - .unwrap() - .iter() - .map(|i| i.data.to_string()) - .collect(); - - debug!( - "loaded {} items of history for user {}", - history.len(), - user.id - ); - - counter!("atuin_history_returned").increment(history.len() as u64); - - Ok(Json(SyncHistoryResponse { history })) -} - -#[instrument(skip_all, fields(user.id = user.id))] -pub(crate) async fn delete<DB: Database>( - UserAuth(user): UserAuth, - state: State<AppState<DB>>, - Json(req): Json<DeleteHistoryRequest>, -) -> Result<Json<MessageResponse>, ErrorResponseStatus<'static>> { - let db = &state.0.database; - - // user_id is the ID of the history, as set by the user (the server has its own ID) - let deleted = db.delete_history(&user, req.client_id).await; - - if let Err(e) = deleted { - error!("failed to delete history: {}", e); - return Err(ErrorResponse::reply("failed to delete history") - .with_status(StatusCode::INTERNAL_SERVER_ERROR)); - } - - Ok(Json(MessageResponse { - message: String::from("deleted OK"), - })) -} - -#[instrument(skip_all, fields(user.id = user.id))] -pub(crate) async fn add<DB: Database>( - UserAuth(user): UserAuth, - state: State<AppState<DB>>, - Json(req): Json<Vec<AddHistoryRequest>>, -) -> Result<(), ErrorResponseStatus<'static>> { - let State(AppState { database, settings }) = state; - - debug!("request to add {} history items", req.len()); - counter!("atuin_history_uploaded").increment(req.len() as u64); - - let mut history: Vec<NewHistory> = req - .into_iter() - .map(|h| NewHistory { - client_id: h.id, - user_id: user.id, - hostname: h.hostname, - timestamp: h.timestamp, - data: h.data, - }) - .collect(); - - history.retain(|h| { - // keep if within limit, or limit is 0 (unlimited) - let keep = h.data.len() <= settings.max_history_length || settings.max_history_length == 0; - - // Don't return an error here. We want to insert as much of the - // history list as we can, so log the error and continue going. - if !keep { - counter!("atuin_history_too_long").increment(1); - - tracing::warn!( - "history too long, got length {}, max {}", - h.data.len(), - settings.max_history_length - ); - } - - keep - }); - - if let Err(e) = database.add_history(&history).await { - error!("failed to add history: {}", e); - - return Err(ErrorResponse::reply("failed to add history") - .with_status(StatusCode::INTERNAL_SERVER_ERROR)); - }; - - Ok(()) -} - -#[derive(serde::Deserialize, Debug)] -pub(crate) struct CalendarQuery { - #[serde(default = "serde_calendar::zero")] - year: i32, - #[serde(default = "serde_calendar::one")] - month: u8, - - #[serde(default = "serde_calendar::utc")] - tz: UtcOffset, -} - -mod serde_calendar { - use time::UtcOffset; - - pub(crate) fn zero() -> i32 { - 0 - } - - pub(crate) fn one() -> u8 { - 1 - } - - pub(crate) fn utc() -> UtcOffset { - UtcOffset::UTC - } -} - -#[instrument(skip_all, fields(user.id = user.id))] -pub(crate) async fn calendar<DB: Database>( - Path(focus): Path<String>, - Query(params): Query<CalendarQuery>, - UserAuth(user): UserAuth, - state: State<AppState<DB>>, -) -> Result<Json<HashMap<u64, TimePeriodInfo>>, ErrorResponseStatus<'static>> { - let focus = focus.as_str(); - - let year = params.year; - let month = Month::try_from(params.month).map_err(|e| ErrorResponseStatus { - error: ErrorResponse { - reason: e.to_string().into(), - }, - status: StatusCode::BAD_REQUEST, - })?; - - let period = match focus { - "year" => TimePeriod::Year, - "month" => TimePeriod::Month { year }, - "day" => TimePeriod::Day { year, month }, - _ => { - return Err(ErrorResponse::reply("invalid focus: use year/month/day") - .with_status(StatusCode::BAD_REQUEST)); - } - }; - - let db = &state.0.database; - let focus = db.calendar(&user, period, params.tz).await.map_err(|_| { - ErrorResponse::reply("failed to query calendar") - .with_status(StatusCode::INTERNAL_SERVER_ERROR) - })?; - - Ok(Json(focus)) -} diff --git a/crates/turtle/src/atuin_server/handlers/mod.rs b/crates/turtle/src/atuin_server/handlers/mod.rs index 322324c4..3b935834 100644 --- a/crates/turtle/src/atuin_server/handlers/mod.rs +++ b/crates/turtle/src/atuin_server/handlers/mod.rs @@ -1,19 +1,16 @@ use crate::atuin_common::api::{ErrorResponse, IndexResponse}; -use crate::atuin_server_database::Database; use axum::{Json, extract::State, http, response::IntoResponse}; use crate::atuin_server::router::AppState; pub(crate) mod health; -pub(crate) mod history; pub(crate) mod record; -pub(crate) mod status; pub(crate) mod user; pub(crate) mod v0; const VERSION: &str = env!("CARGO_PKG_VERSION"); -pub(crate) async fn index<DB: Database>(state: State<AppState<DB>>) -> Json<IndexResponse> { +pub(crate) async fn index(state: State<AppState>) -> Json<IndexResponse> { let homage = r#""Through the fathomless deeps of space swims the star turtle Great A'Tuin, bearing on its back the four giant elephants who carry on their shoulders the mass of the Discworld." -- Sir Terry Pratchett"#; let version = state diff --git a/crates/turtle/src/atuin_server/handlers/status.rs b/crates/turtle/src/atuin_server/handlers/status.rs deleted file mode 100644 index 59be1e5c..00000000 --- a/crates/turtle/src/atuin_server/handlers/status.rs +++ /dev/null @@ -1,45 +0,0 @@ -use axum::{Json, extract::State, http::StatusCode}; -use tracing::instrument; - -use super::{ErrorResponse, ErrorResponseStatus, RespExt}; -use crate::atuin_server::router::{AppState, UserAuth}; -use crate::atuin_server_database::Database; - -use crate::atuin_common::api::*; - -const VERSION: &str = env!("CARGO_PKG_VERSION"); - -#[instrument(skip_all, fields(user.id = user.id))] -pub(crate) async fn status<DB: Database>( - UserAuth(user): UserAuth, - state: State<AppState<DB>>, -) -> Result<Json<StatusResponse>, ErrorResponseStatus<'static>> { - let db = &state.0.database; - - let deleted = db.deleted_history(&user).await.unwrap_or(vec![]); - - let count = match db.count_history_cached(&user).await { - // By default read out the cached value - Ok(count) => count, - - // If that fails, fallback on a full COUNT. Cache is built on a POST - // only - Err(_) => match db.count_history(&user).await { - Ok(count) => count, - Err(_) => { - return Err(ErrorResponse::reply("failed to query history count") - .with_status(StatusCode::INTERNAL_SERVER_ERROR)); - } - }, - }; - - tracing::debug!(user = user.username, "requested sync status"); - - Ok(Json(StatusResponse { - count, - deleted, - username: user.username, - version: VERSION.to_string(), - page_size: state.settings.page_size, - })) -} diff --git a/crates/turtle/src/atuin_server/handlers/user.rs b/crates/turtle/src/atuin_server/handlers/user.rs index 7708d43e..28cebfab 100644 --- a/crates/turtle/src/atuin_server/handlers/user.rs +++ b/crates/turtle/src/atuin_server/handlers/user.rs @@ -16,14 +16,16 @@ use metrics::counter; use rand::rngs::OsRng; use tracing::{debug, error, info, instrument}; -use crate::atuin_common::tls::ensure_crypto_provider; +use crate::{ + atuin_common::tls::ensure_crypto_provider, + atuin_server::database::{ + DbError, + models::{NewSession, NewUser}, + }, +}; use super::{ErrorResponse, ErrorResponseStatus, RespExt}; use crate::atuin_server::router::{AppState, UserAuth}; -use crate::atuin_server_database::{ - Database, DbError, - models::{NewSession, NewUser}, -}; use reqwest::header::CONTENT_TYPE; @@ -63,9 +65,9 @@ async fn send_register_hook(url: &str, username: String, registered: String) { } #[instrument(skip_all, fields(user.username = username.as_str()))] -pub(crate) async fn get<DB: Database>( +pub(crate) async fn get( Path(username): Path<String>, - state: State<AppState<DB>>, + state: State<AppState>, ) -> Result<Json<UserResponse>, ErrorResponseStatus<'static>> { let db = &state.0.database; let user = match db.get_user(username.as_ref()).await { @@ -87,8 +89,8 @@ pub(crate) async fn get<DB: Database>( } #[instrument(skip_all)] -pub(crate) async fn register<DB: Database>( - state: State<AppState<DB>>, +pub(crate) async fn register( + state: State<AppState>, Json(register): Json<RegisterRequest>, ) -> Result<Json<RegisterResponse>, ErrorResponseStatus<'static>> { if !state.settings.open_registration { @@ -163,9 +165,9 @@ pub(crate) async fn register<DB: Database>( } #[instrument(skip_all, fields(user.id = user.id))] -pub(crate) async fn delete<DB: Database>( +pub(crate) async fn delete( UserAuth(user): UserAuth, - state: State<AppState<DB>>, + state: State<AppState>, ) -> Result<Json<DeleteUserResponse>, ErrorResponseStatus<'static>> { debug!("request to delete user {}", user.id); @@ -183,9 +185,9 @@ pub(crate) async fn delete<DB: Database>( } #[instrument(skip_all, fields(user.id = user.id, change_password))] -pub(crate) async fn change_password<DB: Database>( +pub(crate) async fn change_password( UserAuth(mut user): UserAuth, - state: State<AppState<DB>>, + state: State<AppState>, Json(change_password): Json<ChangePasswordRequest>, ) -> Result<Json<ChangePasswordResponse>, ErrorResponseStatus<'static>> { let db = &state.0.database; @@ -213,8 +215,8 @@ pub(crate) async fn change_password<DB: Database>( } #[instrument(skip_all, fields(user.username = login.username.as_str()))] -pub(crate) async fn login<DB: Database>( - state: State<AppState<DB>>, +pub(crate) async fn login( + state: State<AppState>, login: Json<LoginRequest>, ) -> Result<Json<LoginResponse>, ErrorResponseStatus<'static>> { let db = &state.0.database; diff --git a/crates/turtle/src/atuin_server/handlers/v0/record.rs b/crates/turtle/src/atuin_server/handlers/v0/record.rs index 2cc09118..88027547 100644 --- a/crates/turtle/src/atuin_server/handlers/v0/record.rs +++ b/crates/turtle/src/atuin_server/handlers/v0/record.rs @@ -7,14 +7,13 @@ use crate::atuin_server::{ handlers::{ErrorResponse, ErrorResponseStatus, RespExt}, router::{AppState, UserAuth}, }; -use crate::atuin_server_database::Database; use crate::atuin_common::record::{EncryptedData, HostId, Record, RecordIdx, RecordStatus}; #[instrument(skip_all, fields(user.id = user.id))] -pub(crate) async fn post<DB: Database>( +pub(crate) async fn post( UserAuth(user): UserAuth, - state: State<AppState<DB>>, + state: State<AppState>, Json(records): Json<Vec<Record<EncryptedData>>>, ) -> Result<(), ErrorResponseStatus<'static>> { let State(AppState { database, settings }) = state; @@ -51,9 +50,9 @@ pub(crate) async fn post<DB: Database>( } #[instrument(skip_all, fields(user.id = user.id))] -pub(crate) async fn index<DB: Database>( +pub(crate) async fn index( UserAuth(user): UserAuth, - state: State<AppState<DB>>, + state: State<AppState>, ) -> Result<Json<RecordStatus>, ErrorResponseStatus<'static>> { let State(AppState { database, @@ -84,10 +83,10 @@ pub(crate) struct NextParams { } #[instrument(skip_all, fields(user.id = user.id))] -pub(crate) async fn next<DB: Database>( +pub(crate) async fn next( params: Query<NextParams>, UserAuth(user): UserAuth, - state: State<AppState<DB>>, + state: State<AppState>, ) -> Result<Json<Vec<Record<EncryptedData>>>, ErrorResponseStatus<'static>> { let State(AppState { database, diff --git a/crates/turtle/src/atuin_server/handlers/v0/store.rs b/crates/turtle/src/atuin_server/handlers/v0/store.rs index 8269d6b3..f0aa1b36 100644 --- a/crates/turtle/src/atuin_server/handlers/v0/store.rs +++ b/crates/turtle/src/atuin_server/handlers/v0/store.rs @@ -7,16 +7,15 @@ use crate::atuin_server::{ handlers::{ErrorResponse, ErrorResponseStatus, RespExt}, router::{AppState, UserAuth}, }; -use crate::atuin_server_database::Database; #[derive(Deserialize)] pub(crate) struct DeleteParams {} #[instrument(skip_all, fields(user.id = user.id))] -pub(crate) async fn delete<DB: Database>( +pub(crate) async fn delete( _params: Query<DeleteParams>, UserAuth(user): UserAuth, - state: State<AppState<DB>>, + state: State<AppState>, ) -> Result<(), ErrorResponseStatus<'static>> { let State(AppState { database, |
