mirror of
https://github.com/atuinsh/atuin.git
synced 2024-12-26 00:50:22 +01:00
feat: add store push --force
This will 1. Wipe the remote store 2. Upload all of the local store to remote Imagine the scenario where you end up with some mixed keys locally :( You confirm this with ``` atuin store verify ``` You then fix it locally with ``` atuin store purge ``` Ensure that your local changes are reflected remotely with ``` atuin store push --force ``` and then (another PR, coming soon), update all other hosts with ``` atuin store pull --force ```
This commit is contained in:
parent
3c420f85f6
commit
c9a453289e
@ -287,6 +287,17 @@ impl<'a> Client<'a> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn delete_store(&self) -> Result<()> {
|
||||||
|
let url = format!("{}/api/v0/store", self.sync_addr);
|
||||||
|
let url = Url::parse(url.as_str())?;
|
||||||
|
|
||||||
|
let resp = self.client.delete(url).send().await?;
|
||||||
|
|
||||||
|
handle_resp_error(resp).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn post_records(&self, records: &[Record<EncryptedData>]) -> Result<()> {
|
pub async fn post_records(&self, records: &[Record<EncryptedData>]) -> Result<()> {
|
||||||
let url = format!("{}/api/v0/record", self.sync_addr);
|
let url = format!("{}/api/v0/record", self.sync_addr);
|
||||||
let url = Url::parse(url.as_str())?;
|
let url = Url::parse(url.as_str())?;
|
||||||
|
@ -53,15 +53,16 @@ pub trait Database: Sized + Clone + Send + Sync + 'static {
|
|||||||
async fn get_user(&self, username: &str) -> DbResult<User>;
|
async fn get_user(&self, username: &str) -> DbResult<User>;
|
||||||
async fn get_user_session(&self, u: &User) -> DbResult<Session>;
|
async fn get_user_session(&self, u: &User) -> DbResult<Session>;
|
||||||
async fn add_user(&self, user: &NewUser) -> DbResult<i64>;
|
async fn add_user(&self, user: &NewUser) -> DbResult<i64>;
|
||||||
async fn delete_user(&self, u: &User) -> DbResult<()>;
|
|
||||||
async fn update_user_password(&self, u: &User) -> DbResult<()>;
|
async fn update_user_password(&self, u: &User) -> DbResult<()>;
|
||||||
|
|
||||||
async fn total_history(&self) -> DbResult<i64>;
|
async fn total_history(&self) -> DbResult<i64>;
|
||||||
async fn count_history(&self, user: &User) -> DbResult<i64>;
|
async fn count_history(&self, user: &User) -> DbResult<i64>;
|
||||||
async fn count_history_cached(&self, user: &User) -> DbResult<i64>;
|
async fn count_history_cached(&self, user: &User) -> DbResult<i64>;
|
||||||
|
|
||||||
|
async fn delete_user(&self, u: &User) -> DbResult<()>;
|
||||||
async fn delete_history(&self, user: &User, id: String) -> DbResult<()>;
|
async fn delete_history(&self, user: &User, id: String) -> DbResult<()>;
|
||||||
async fn deleted_history(&self, user: &User) -> DbResult<Vec<String>>;
|
async fn deleted_history(&self, user: &User) -> DbResult<Vec<String>>;
|
||||||
|
async fn delete_store(&self, user: &User) -> DbResult<()>;
|
||||||
|
|
||||||
async fn add_records(&self, user: &User, record: &[Record<EncryptedData>]) -> DbResult<()>;
|
async fn add_records(&self, user: &User, record: &[Record<EncryptedData>]) -> DbResult<()>;
|
||||||
async fn next_records(
|
async fn next_records(
|
||||||
|
@ -133,6 +133,19 @@ impl Database for Postgres {
|
|||||||
Ok(res.0 as i64)
|
Ok(res.0 as i64)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn delete_store(&self, user: &User) -> DbResult<()> {
|
||||||
|
sqlx::query(
|
||||||
|
"delete from store
|
||||||
|
where user_id = $1",
|
||||||
|
)
|
||||||
|
.bind(user.id)
|
||||||
|
.execute(&self.pool)
|
||||||
|
.await
|
||||||
|
.map_err(fix_error)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
async fn delete_history(&self, user: &User, id: String) -> DbResult<()> {
|
async fn delete_history(&self, user: &User, id: String) -> DbResult<()> {
|
||||||
sqlx::query(
|
sqlx::query(
|
||||||
"update history
|
"update history
|
||||||
|
@ -1 +1,2 @@
|
|||||||
pub(crate) mod record;
|
pub(crate) mod record;
|
||||||
|
pub(crate) mod store;
|
||||||
|
37
atuin-server/src/handlers/v0/store.rs
Normal file
37
atuin-server/src/handlers/v0/store.rs
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
use axum::{extract::Query, extract::State, http::StatusCode};
|
||||||
|
use metrics::counter;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use tracing::{error, instrument};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
handlers::{ErrorResponse, ErrorResponseStatus, RespExt},
|
||||||
|
router::{AppState, UserAuth},
|
||||||
|
};
|
||||||
|
use atuin_server_database::Database;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct DeleteParams {}
|
||||||
|
|
||||||
|
#[instrument(skip_all, fields(user.id = user.id))]
|
||||||
|
pub async fn delete<DB: Database>(
|
||||||
|
_params: Query<DeleteParams>,
|
||||||
|
UserAuth(user): UserAuth,
|
||||||
|
state: State<AppState<DB>>,
|
||||||
|
) -> Result<(), ErrorResponseStatus<'static>> {
|
||||||
|
let State(AppState {
|
||||||
|
database,
|
||||||
|
settings: _,
|
||||||
|
}) = state;
|
||||||
|
|
||||||
|
if let Err(e) = database.delete_store(&user).await {
|
||||||
|
counter!("atuin_store_delete_failed", 1);
|
||||||
|
error!("failed to delete store {e:?}");
|
||||||
|
|
||||||
|
return Err(ErrorResponse::reply("failed to delete store")
|
||||||
|
.with_status(StatusCode::INTERNAL_SERVER_ERROR));
|
||||||
|
}
|
||||||
|
|
||||||
|
counter!("atuin_store_deleted", 1);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
@ -127,7 +127,8 @@ pub fn router<DB: Database>(database: DB, settings: Settings<DB::Settings>) -> R
|
|||||||
.route("/record/next", get(handlers::record::next))
|
.route("/record/next", get(handlers::record::next))
|
||||||
.route("/api/v0/record", post(handlers::v0::record::post))
|
.route("/api/v0/record", post(handlers::v0::record::post))
|
||||||
.route("/api/v0/record", get(handlers::v0::record::index))
|
.route("/api/v0/record", get(handlers::v0::record::index))
|
||||||
.route("/api/v0/record/next", get(handlers::v0::record::next));
|
.route("/api/v0/record/next", get(handlers::v0::record::next))
|
||||||
|
.route("/api/v0/store", delete(handlers::v0::store::delete));
|
||||||
|
|
||||||
let path = settings.path.as_str();
|
let path = settings.path.as_str();
|
||||||
if path.is_empty() {
|
if path.is_empty() {
|
||||||
|
@ -4,6 +4,7 @@ use eyre::Result;
|
|||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use atuin_client::{
|
use atuin_client::{
|
||||||
|
api_client::Client,
|
||||||
record::sync::Operation,
|
record::sync::Operation,
|
||||||
record::{sqlite_store::SqliteStore, sync},
|
record::{sqlite_store::SqliteStore, sync},
|
||||||
settings::Settings,
|
settings::Settings,
|
||||||
@ -18,11 +19,34 @@ pub struct Push {
|
|||||||
/// The host to push, in the form of a UUID host ID. Defaults to the current host.
|
/// The host to push, in the form of a UUID host ID. Defaults to the current host.
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
pub host: Option<Uuid>,
|
pub host: Option<Uuid>,
|
||||||
|
|
||||||
|
/// Force push records
|
||||||
|
/// This will override both host and tag, to be all hosts and all tags. First clear the remote store, then upload all of the
|
||||||
|
/// local store
|
||||||
|
#[arg(long, default_value = "false")]
|
||||||
|
pub force: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Push {
|
impl Push {
|
||||||
pub async fn run(&self, settings: &Settings, store: SqliteStore) -> Result<()> {
|
pub async fn run(&self, settings: &Settings, store: SqliteStore) -> Result<()> {
|
||||||
let host_id = Settings::host_id().expect("failed to get host_id");
|
let host_id = Settings::host_id().expect("failed to get host_id");
|
||||||
|
|
||||||
|
if self.force {
|
||||||
|
println!("Forcing remote store overwrite!");
|
||||||
|
println!("Clearing remote store");
|
||||||
|
|
||||||
|
let client = Client::new(
|
||||||
|
&settings.sync_address,
|
||||||
|
&settings.session_token,
|
||||||
|
settings.network_connect_timeout,
|
||||||
|
settings.network_timeout * 10, // we may be deleting a lot of data... so up the
|
||||||
|
// timeout
|
||||||
|
)
|
||||||
|
.expect("failed to create client");
|
||||||
|
|
||||||
|
client.delete_store().await?;
|
||||||
|
}
|
||||||
|
|
||||||
// We can actually just use the existing diff/etc to push
|
// We can actually just use the existing diff/etc to push
|
||||||
// 1. Diff
|
// 1. Diff
|
||||||
// 2. Get operations
|
// 2. Get operations
|
||||||
@ -40,6 +64,10 @@ impl Push {
|
|||||||
|
|
||||||
// push, so yes plz to uploads!
|
// push, so yes plz to uploads!
|
||||||
Operation::Upload { host, tag, .. } => {
|
Operation::Upload { host, tag, .. } => {
|
||||||
|
if self.force {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(h) = self.host {
|
if let Some(h) = self.host {
|
||||||
if HostId(h) != *host {
|
if HostId(h) != *host {
|
||||||
return false;
|
return false;
|
||||||
|
Loading…
Reference in New Issue
Block a user