Return existing doc in create if one with the same path already exists

This commit is contained in:
Andras Schmelczer 2024-12-10 22:22:14 +00:00
parent 30d998e437
commit 0c3e74e2b8
No known key found for this signature in database
GPG key ID: FC8F2C3D3D1A718C
3 changed files with 107 additions and 16 deletions

View file

@ -35,6 +35,7 @@
- add clap - add clap
- add auth middleware - add auth middleware
- add request logs - add request logs
- the is deleted logic is bad and we'll always read the previous version instead of the deleted
- CI for: - CI for:
- publish reconcile - publish reconcile
- cross-platform build server - cross-platform build server

View file

@ -129,6 +129,42 @@ impl Database {
.context("Cannot fetch latest document version") .context("Cannot fetch latest document version")
} }
pub async fn get_latest_document_version_by_path(
&self,
vault: &VaultId,
relative_path: &str,
transaction: Option<&mut Transaction<'_>>,
) -> Result<Option<StoredDocumentVersion>> {
let query = sqlx::query_as!(
StoredDocumentVersion,
r#"
select
vault_id,
document_id as "document_id: uuid::Uuid",
version_id,
created_date as "created_date: chrono::DateTime<Utc>",
updated_date as "updated_date: chrono::DateTime<Utc>",
relative_path,
content,
is_binary,
is_deleted
from documents
where vault_id = ? and relative_path = ? and is_deleted = false
ORDER BY version_id DESC
LIMIT 1
"#,
vault,
relative_path
);
if let Some(transaction) = transaction {
query.fetch_optional(&mut **transaction).await
} else {
query.fetch_optional(&self.connection_pool).await
}
.context("Cannot fetch latest document version by path")
}
pub async fn get_document_version( pub async fn get_document_version(
&self, &self,
vault: &VaultId, vault: &VaultId,

View file

@ -9,12 +9,12 @@ use axum_extra::{
}; };
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::Deserialize; use serde::Deserialize;
use sync_lib::base64_to_bytes; use sync_lib::{base64_to_bytes, base64_to_string};
use super::{auth::auth, requests::CreateDocumentVersion}; use super::{auth::auth, requests::CreateDocumentVersion};
use crate::{ use crate::{
app_state::AppState, app_state::AppState,
database::models::{DocumentVersionWithoutContent, StoredDocumentVersion, VaultId}, database::models::{DocumentVersion, StoredDocumentVersion, VaultId},
errors::{client_error, server_error, SyncServerError}, errors::{client_error, server_error, SyncServerError},
}; };
@ -24,34 +24,88 @@ pub struct PathParams {
vault_id: VaultId, vault_id: VaultId,
} }
/// Create a new document in case a document with the same doesn't exist
/// already. If a document with the same path exists, a new version is created
/// with their content merged.
#[axum::debug_handler] #[axum::debug_handler]
pub async fn create_document( pub async fn create_document(
TypedHeader(auth_header): TypedHeader<Authorization<Bearer>>, TypedHeader(auth_header): TypedHeader<Authorization<Bearer>>,
Path(PathParams { vault_id }): Path<PathParams>, Path(PathParams { vault_id }): Path<PathParams>,
State(state): State<AppState>, State(state): State<AppState>,
Json(request): Json<CreateDocumentVersion>, Json(request): Json<CreateDocumentVersion>,
) -> Result<Json<DocumentVersionWithoutContent>, SyncServerError> { ) -> Result<Json<DocumentVersion>, SyncServerError> {
auth(&state, auth_header.token())?; auth(&state, auth_header.token())?;
let new_version = StoredDocumentVersion { let mut transaction = state
vault_id, .database
document_id: uuid::Uuid::new_v4(), .create_transaction()
version_id: 0, .await
content: base64_to_bytes(&request.content_base64) .map_err(server_error)?;
.context("Cannot convert base64 encoded content to bytes")
.map_err(client_error)?, let maybe_existing_version = state
created_date: request.created_date, .database
relative_path: request.relative_path, .get_latest_document_version_by_path(
updated_date: chrono::Utc::now(), &vault_id,
is_binary: request.is_binary, &request.relative_path,
is_deleted: false, Some(&mut transaction),
)
.await
.map_err(server_error)?;
let new_version = if let Some(existing_version) = maybe_existing_version {
let merged_content = if request.is_binary {
base64_to_bytes(&request.content_base64)
.context("Failed to decode base64 content in request")
.map_err(client_error)?
} else {
reconcile::reconcile(
"", // the empty string is the first common parent of the two documents
&existing_version.content_as_string(),
&base64_to_string(&request.content_base64)
.context("Failed to decode base64 content in request")
.map_err(client_error)?,
)
.into_bytes()
};
StoredDocumentVersion {
vault_id,
document_id: existing_version.document_id,
version_id: existing_version.version_id + 1,
content: merged_content,
created_date: request.created_date,
relative_path: request.relative_path,
updated_date: chrono::Utc::now(),
is_binary: request.is_binary,
is_deleted: false,
}
} else {
StoredDocumentVersion {
vault_id,
document_id: uuid::Uuid::new_v4(),
version_id: 0,
content: base64_to_bytes(&request.content_base64)
.context("Cannot convert base64 encoded content to bytes")
.map_err(client_error)?,
created_date: request.created_date,
relative_path: request.relative_path,
updated_date: chrono::Utc::now(),
is_binary: request.is_binary,
is_deleted: false,
}
}; };
state state
.database .database
.insert_document_version(&new_version, None) .insert_document_version(&new_version, Some(&mut transaction))
.await .await
.map_err(server_error)?; .map_err(server_error)?;
transaction
.commit()
.await
.context("Failed to commit successful transaction")
.map_err(server_error)?;
Ok(Json(new_version.into())) Ok(Json(new_version.into()))
} }