diff options
author | Ellie Huxtable <ellie@elliehuxtable.com> | 2023-03-20 09:26:54 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-03-20 09:26:54 +0000 |
commit | dcd77749dd1fdf6b0c8183bfbdf4f97bf238ebe4 (patch) | |
tree | 97c623911eeb52da65c2b3fd80092f2c86f3dd18 /atuin-server | |
parent | edcd477153d00944c5dae16ec3ba69e339e1450c (diff) |
Add history deletion (#791)
* Drop events. I'd still like to do them, but differently
* Start adding delete api stuff
* Set mailmap
* Delete delete delete
* Fix tests
* Make clippy happy
Diffstat (limited to 'atuin-server')
-rw-r--r-- | atuin-server/migrations/20230315220537_drop-events.sql | 2 | ||||
-rw-r--r-- | atuin-server/migrations/20230315224203_create-deleted.sql | 5 | ||||
-rw-r--r-- | atuin-server/src/database.rs | 46 | ||||
-rw-r--r-- | atuin-server/src/handlers/history.rs | 22 | ||||
-rw-r--r-- | atuin-server/src/handlers/mod.rs | 1 | ||||
-rw-r--r-- | atuin-server/src/handlers/status.rs | 29 | ||||
-rw-r--r-- | atuin-server/src/router.rs | 4 |
7 files changed, 108 insertions, 1 deletions
diff --git a/atuin-server/migrations/20230315220537_drop-events.sql b/atuin-server/migrations/20230315220537_drop-events.sql new file mode 100644 index 000000000..fe3cae170 --- /dev/null +++ b/atuin-server/migrations/20230315220537_drop-events.sql @@ -0,0 +1,2 @@ +-- Add migration script here +drop table events; diff --git a/atuin-server/migrations/20230315224203_create-deleted.sql b/atuin-server/migrations/20230315224203_create-deleted.sql new file mode 100644 index 000000000..9a9e6263f --- /dev/null +++ b/atuin-server/migrations/20230315224203_create-deleted.sql @@ -0,0 +1,5 @@ +-- Add migration script here +alter table history add column if not exists deleted_at timestamp; + +-- queries will all be selecting the ids of history for a user, that has been deleted +create index if not exists history_deleted_index on history(client_id, user_id, deleted_at); diff --git a/atuin-server/src/database.rs b/atuin-server/src/database.rs index ef6c6d859..7f3e5dac4 100644 --- a/atuin-server/src/database.rs +++ b/atuin-server/src/database.rs @@ -4,6 +4,9 @@ use async_trait::async_trait; use chrono::{Datelike, TimeZone}; use chronoutil::RelativeDuration; use sqlx::{postgres::PgPoolOptions, Result}; + +use sqlx::Row; + use tracing::{debug, instrument, warn}; use super::{ @@ -28,6 +31,9 @@ pub trait Database { async fn count_history(&self, user: &User) -> Result<i64>; async fn count_history_cached(&self, user: &User) -> Result<i64>; + async fn delete_history(&self, user: &User, id: String) -> Result<()>; + async fn deleted_history(&self, user: &User) -> Result<Vec<String>>; + async fn count_history_range( &self, user: &User, @@ -141,6 +147,46 @@ impl Database for Postgres { Ok(res.0 as i64) } + async fn delete_history(&self, user: &User, id: String) -> Result<()> { + sqlx::query( + "update history + set deleted_at = $3 + where user_id = $1 + and client_id = $2 + and deleted_at is null", // don't just keep setting it + ) + .bind(user.id) + .bind(id) + .bind(chrono::Utc::now().naive_utc()) + .fetch_all(&self.pool) + .await?; + + Ok(()) + } + + #[instrument(skip_all)] + async fn deleted_history(&self, user: &User) -> Result<Vec<String>> { + // The cache is new, and the user might not yet have a cache value. + // They will have one as soon as they post up some new history, but handle that + // edge case. + + let res = sqlx::query( + "select client_id from history + where user_id = $1 + and deleted_at is not null", + ) + .bind(user.id) + .fetch_all(&self.pool) + .await?; + + let res = res + .iter() + .map(|row| row.get::<String, _>("client_id")) + .collect(); + + Ok(res) + } + #[instrument(skip_all)] async fn count_history_range( &self, diff --git a/atuin-server/src/handlers/history.rs b/atuin-server/src/handlers/history.rs index 7cf18323d..9a7cb2458 100644 --- a/atuin-server/src/handlers/history.rs +++ b/atuin-server/src/handlers/history.rs @@ -75,6 +75,28 @@ pub async fn list<DB: Database>( } #[instrument(skip_all, fields(user.id = user.id))] +pub async fn delete<DB: Database>( + user: User, + 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 async fn add<DB: Database>( user: User, state: State<AppState<DB>>, diff --git a/atuin-server/src/handlers/mod.rs b/atuin-server/src/handlers/mod.rs index 082ae4711..35d32f6f6 100644 --- a/atuin-server/src/handlers/mod.rs +++ b/atuin-server/src/handlers/mod.rs @@ -2,6 +2,7 @@ use atuin_common::api::{ErrorResponse, IndexResponse}; use axum::{response::IntoResponse, Json}; pub mod history; +pub mod status; pub mod user; const VERSION: &str = env!("CARGO_PKG_VERSION"); diff --git a/atuin-server/src/handlers/status.rs b/atuin-server/src/handlers/status.rs new file mode 100644 index 000000000..9c7ef7792 --- /dev/null +++ b/atuin-server/src/handlers/status.rs @@ -0,0 +1,29 @@ +use axum::{extract::State, Json}; +use http::StatusCode; +use tracing::instrument; + +use super::{ErrorResponse, ErrorResponseStatus, RespExt}; +use crate::{database::Database, models::User, router::AppState}; + +use atuin_common::api::*; + +#[instrument(skip_all, fields(user.id = user.id))] +pub async fn status<DB: Database>( + user: User, + state: State<AppState<DB>>, +) -> Result<Json<StatusResponse>, ErrorResponseStatus<'static>> { + let db = &state.0.database; + + let history_count = db.count_history_cached(&user).await; + let deleted = db.deleted_history(&user).await; + + if history_count.is_err() || deleted.is_err() { + return Err(ErrorResponse::reply("failed to query history count") + .with_status(StatusCode::INTERNAL_SERVER_ERROR)); + } + + Ok(Json(StatusResponse { + count: history_count.unwrap(), + deleted: deleted.unwrap(), + })) +} diff --git a/atuin-server/src/router.rs b/atuin-server/src/router.rs index c4f7d3099..58aac3bd4 100644 --- a/atuin-server/src/router.rs +++ b/atuin-server/src/router.rs @@ -2,7 +2,7 @@ use async_trait::async_trait; use axum::{ extract::FromRequestParts, response::IntoResponse, - routing::{get, post}, + routing::{delete, get, post}, Router, }; use eyre::Result; @@ -68,7 +68,9 @@ pub fn router<DB: Database + Clone + Send + Sync + 'static>( .route("/sync/count", get(handlers::history::count)) .route("/sync/history", get(handlers::history::list)) .route("/sync/calendar/:focus", get(handlers::history::calendar)) + .route("/sync/status", get(handlers::status::status)) .route("/history", post(handlers::history::add)) + .route("/history", delete(handlers::history::delete)) .route("/user/:username", get(handlers::user::get)) .route("/register", post(handlers::user::register)) .route("/login", post(handlers::user::login)); |