This commit is contained in:
Andras Schmelczer 2026-04-06 13:01:47 +01:00
parent 0e3e5a99cd
commit d034ad5cb3
50 changed files with 6515 additions and 1492 deletions

View file

@ -0,0 +1,147 @@
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::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)?;
let new_version = StoredDocumentVersion {
vault_update_id: last_update_id + 1,
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,
};
state
.database
.insert_document_version(&vault_id, &new_version, Some(transaction))
.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()))
}