..
This commit is contained in:
parent
17a1f4d060
commit
c9cf3239db
10 changed files with 200 additions and 509 deletions
|
|
@ -14,7 +14,6 @@ mod ping;
|
|||
mod rate_limit;
|
||||
mod requests;
|
||||
mod responses;
|
||||
mod restore_document_version;
|
||||
mod update_document;
|
||||
mod websocket;
|
||||
|
||||
|
|
@ -174,10 +173,6 @@ fn get_authed_routes(app_state: AppState) -> Router<AppState> {
|
|||
"/vaults/:vault_id/documents/:document_id",
|
||||
delete(delete_document::delete_document),
|
||||
)
|
||||
.route(
|
||||
"/vaults/:vault_id/documents/:document_id/restore",
|
||||
post(restore_document_version::restore_document_version),
|
||||
)
|
||||
.route(
|
||||
"/vaults/:vault_id/history",
|
||||
get(fetch_vault_history::fetch_vault_history),
|
||||
|
|
|
|||
|
|
@ -1,186 +0,0 @@
|
|||
use anyhow::anyhow;
|
||||
use axum::{
|
||||
Extension, Json,
|
||||
extract::{Path, State},
|
||||
};
|
||||
use axum_extra::TypedHeader;
|
||||
use log::{debug, info};
|
||||
use serde::Deserialize;
|
||||
|
||||
use super::device_id_header::DeviceIdHeader;
|
||||
use crate::{
|
||||
app_state::{
|
||||
AppState,
|
||||
database::{
|
||||
InsertBroadcast,
|
||||
models::{
|
||||
DocumentId, DocumentVersionWithoutContent, StoredDocumentVersion, VaultId,
|
||||
VaultUpdateId,
|
||||
},
|
||||
},
|
||||
},
|
||||
config::user_config::User,
|
||||
errors::{
|
||||
SyncServerError, client_error, not_found_error, server_error, write_transaction_error,
|
||||
},
|
||||
utils::{find_first_available_path::find_first_available_path, normalize::normalize},
|
||||
};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct RestorePathParams {
|
||||
#[serde(deserialize_with = "normalize")]
|
||||
vault_id: VaultId,
|
||||
|
||||
document_id: DocumentId,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RestoreDocumentVersionRequest {
|
||||
pub vault_update_id: VaultUpdateId,
|
||||
}
|
||||
|
||||
#[axum::debug_handler]
|
||||
pub async fn restore_document_version(
|
||||
Path(RestorePathParams {
|
||||
vault_id,
|
||||
document_id,
|
||||
}): Path<RestorePathParams>,
|
||||
Extension(user): Extension<User>,
|
||||
TypedHeader(device_id): TypedHeader<DeviceIdHeader>,
|
||||
State(state): State<AppState>,
|
||||
Json(request): Json<RestoreDocumentVersionRequest>,
|
||||
) -> Result<Json<DocumentVersionWithoutContent>, SyncServerError> {
|
||||
debug!(
|
||||
"Restoring document `{document_id}` in vault `{vault_id}` to version `{}`",
|
||||
request.vault_update_id
|
||||
);
|
||||
|
||||
if request.vault_update_id <= 0 {
|
||||
return Err(client_error(anyhow!(
|
||||
"Invalid vault_update_id: `{}`",
|
||||
request.vault_update_id
|
||||
)));
|
||||
}
|
||||
|
||||
let mut transaction = state
|
||||
.database
|
||||
.create_write_transaction(&vault_id)
|
||||
.await
|
||||
.map_err(write_transaction_error)?;
|
||||
|
||||
let target_version = state
|
||||
.database
|
||||
.get_document_version(&vault_id, request.vault_update_id, Some(&mut *transaction))
|
||||
.await
|
||||
.map_err(server_error)?
|
||||
.ok_or_else(|| {
|
||||
not_found_error(anyhow!("Version `{}` not found", request.vault_update_id))
|
||||
})?;
|
||||
|
||||
if target_version.document_id != document_id {
|
||||
transaction.rollback().await.map_err(server_error)?;
|
||||
return Err(not_found_error(anyhow!(
|
||||
"Version `{}` does not belong to document `{document_id}`",
|
||||
request.vault_update_id,
|
||||
)));
|
||||
}
|
||||
|
||||
if target_version.is_deleted {
|
||||
transaction.rollback().await.map_err(server_error)?;
|
||||
return Err(client_error(anyhow!(
|
||||
"Cannot restore to a deleted version `{}`",
|
||||
request.vault_update_id,
|
||||
)));
|
||||
}
|
||||
|
||||
let existing = state
|
||||
.database
|
||||
.get_latest_non_deleted_document_by_path(
|
||||
&vault_id,
|
||||
&target_version.relative_path,
|
||||
Some(&mut *transaction),
|
||||
)
|
||||
.await
|
||||
.map_err(server_error)?;
|
||||
|
||||
let restore_path = if let Some(existing_doc) = &existing
|
||||
&& existing_doc.document_id != document_id
|
||||
{
|
||||
find_first_available_path(
|
||||
&vault_id,
|
||||
&target_version.relative_path,
|
||||
&state.database,
|
||||
&mut transaction,
|
||||
)
|
||||
.await
|
||||
.map_err(server_error)?
|
||||
} else {
|
||||
target_version.relative_path.clone()
|
||||
};
|
||||
|
||||
let last_update_id = state
|
||||
.database
|
||||
.get_max_update_id_in_vault(&vault_id, Some(&mut *transaction))
|
||||
.await
|
||||
.map_err(server_error)?;
|
||||
|
||||
// The current latest (pre-restore) is our baseline for deciding
|
||||
// whether content and/or path actually change.
|
||||
let current_latest = state
|
||||
.database
|
||||
.get_latest_document(&vault_id, &document_id, Some(&mut *transaction))
|
||||
.await
|
||||
.map_err(server_error)?;
|
||||
|
||||
let new_version = StoredDocumentVersion {
|
||||
vault_update_id: last_update_id + 1,
|
||||
creation_vault_update_id: target_version.creation_vault_update_id,
|
||||
document_id,
|
||||
relative_path: restore_path,
|
||||
content: target_version.content,
|
||||
updated_date: chrono::Utc::now(),
|
||||
is_deleted: false,
|
||||
user_id: user.name.clone(),
|
||||
device_id: device_id.0.clone(),
|
||||
has_been_merged: false,
|
||||
};
|
||||
|
||||
let (content_changed, path_changed) = match ¤t_latest {
|
||||
Some(prev) => (
|
||||
prev.content != new_version.content || prev.is_deleted,
|
||||
// Mirror `update_document`: `path_changed` is true when the
|
||||
// stored path differs from either the prior stored path (peers
|
||||
// need to learn about the move) *or* from the path the caller
|
||||
// implicitly requested (`target_version.relative_path`, so the
|
||||
// origin learns if the server deduped its requested restore
|
||||
// path).
|
||||
prev.relative_path != new_version.relative_path
|
||||
|| target_version.relative_path != new_version.relative_path,
|
||||
),
|
||||
// No prior version (shouldn't happen in practice — target_version
|
||||
// already proved the document exists — but treat defensively).
|
||||
None => (true, true),
|
||||
};
|
||||
|
||||
state
|
||||
.database
|
||||
.insert_document_version(
|
||||
&vault_id,
|
||||
&new_version,
|
||||
transaction,
|
||||
InsertBroadcast {
|
||||
content_changed,
|
||||
path_changed,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.map_err(server_error)?;
|
||||
|
||||
info!(
|
||||
"Restored document `{document_id}` to version `{}` as new version `{}`",
|
||||
request.vault_update_id, new_version.vault_update_id
|
||||
);
|
||||
|
||||
Ok(Json(new_version.into()))
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue