Remove path repetition

This commit is contained in:
Andras Schmelczer 2026-09-19 19:58:57 +01:00
commit 2a7a19fdf0
10 changed files with 65 additions and 149 deletions

View file

@ -11,3 +11,19 @@ pub mod put_file_manifest;
mod utils;
pub mod vault_snapshot;
pub mod websocket;
use serde::{Deserialize, Deserializer};
use crate::{app_state::database::models::VaultId, utils::normalize_vault_id::normalize_vault_id};
#[derive(Debug)]
pub struct VaultPath(pub VaultId);
impl<'de> Deserialize<'de> for VaultPath {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
normalize_vault_id(deserializer).map(Self)
}
}

View file

@ -5,21 +5,12 @@ use axum::{
use log::debug;
use serde::Deserialize;
use super::VaultPath;
use crate::{
app_state::{
AppState,
database::models::{EventBatch, VaultId},
},
app_state::{AppState, database::models::EventBatch},
errors::{SyncServerError, client_error, server_error},
utils::normalize_vault_id::normalize_vault_id,
};
#[derive(Deserialize)]
pub struct EventsPath {
#[serde(deserialize_with = "normalize_vault_id")]
vault_id: VaultId,
}
#[derive(Deserialize)]
pub struct EventsQuery {
pub after: i64,
@ -27,13 +18,13 @@ pub struct EventsQuery {
#[axum::debug_handler]
pub async fn events(
Path(path): Path<EventsPath>,
Path(VaultPath(vault_id)): Path<VaultPath>,
Query(query): Query<EventsQuery>,
State(state): State<AppState>,
) -> Result<Json<EventBatch>, SyncServerError> {
debug!(
"Fetching events after `{}` for vault `{}`",
query.after, path.vault_id
query.after, vault_id
);
if query.after < 0 {
@ -42,7 +33,7 @@ pub async fn events(
state
.database
.events_after(&path.vault_id, query.after)
.events_after(&vault_id, query.after)
.await
.map(Json)
.map_err(server_error)

View file

@ -4,33 +4,23 @@ use axum::{
extract::{Path, State},
};
use log::debug;
use serde::Deserialize;
use super::VaultPath;
use crate::{
app_state::{
AppState,
database::models::{DocumentId, VaultId, VaultUpdateId},
database::models::{DocumentId, VaultUpdateId},
},
errors::{SyncServerError, not_found_error, server_error},
utils::normalize_vault_id::normalize_vault_id,
};
#[derive(Deserialize)]
pub struct FetchDocumentVersionContentPathParams {
#[serde(deserialize_with = "normalize_vault_id")]
vault_id: VaultId,
document_id: DocumentId,
vault_update_id: VaultUpdateId,
}
#[axum::debug_handler]
pub async fn fetch_document_version_content(
Path(FetchDocumentVersionContentPathParams {
vault_id,
document_id,
vault_update_id,
}): Path<FetchDocumentVersionContentPathParams>,
Path((VaultPath(vault_id), document_id, vault_update_id)): Path<(
VaultPath,
DocumentId,
VaultUpdateId,
)>,
State(state): State<AppState>,
) -> Result<Bytes, SyncServerError> {
debug!(

View file

@ -4,31 +4,19 @@ use axum::{
extract::{Path, State},
};
use log::debug;
use serde::Deserialize;
use super::VaultPath;
use crate::{
app_state::{
AppState,
database::models::{DocumentId, DocumentVersion, VaultId},
database::models::{DocumentId, DocumentVersion},
},
errors::{SyncServerError, not_found_error, server_error},
utils::normalize_vault_id::normalize_vault_id,
};
#[derive(Deserialize)]
pub struct FetchLatestDocumentVersionPathParams {
#[serde(deserialize_with = "normalize_vault_id")]
vault_id: VaultId,
document_id: DocumentId,
}
#[axum::debug_handler]
pub async fn fetch_latest_document_version(
Path(FetchLatestDocumentVersionPathParams {
vault_id,
document_id,
}): Path<FetchLatestDocumentVersionPathParams>,
Path((VaultPath(vault_id), document_id)): Path<(VaultPath, DocumentId)>,
State(state): State<AppState>,
) -> Result<Json<DocumentVersion>, SyncServerError> {
debug!("Fetching latest document version for document `{document_id}` in vault `{vault_id}`");

View file

@ -3,36 +3,26 @@ use axum::{
extract::{Path, State},
};
use log::debug;
use serde::Deserialize;
use super::VaultPath;
use crate::{
app_state::{
AppState,
database::{
Database,
models::{FileManifest, VaultId},
},
database::{Database, models::FileManifest},
},
errors::{SyncServerError, server_error},
utils::normalize_vault_id::normalize_vault_id,
};
#[derive(Deserialize)]
pub struct GetFileManifestPath {
#[serde(deserialize_with = "normalize_vault_id")]
vault_id: VaultId,
}
#[axum::debug_handler]
pub async fn get_file_manifest(
Path(path): Path<GetFileManifestPath>,
Path(VaultPath(vault_id)): Path<VaultPath>,
State(state): State<AppState>,
) -> Result<Json<FileManifest>, SyncServerError> {
debug!("Fetching file manifest for vault `{}`", path.vault_id);
debug!("Fetching file manifest for vault `{vault_id}`");
let mut transaction = state
.database
.create_readonly_transaction(&path.vault_id)
.create_readonly_transaction(&vault_id)
.await
.map_err(server_error)?;

View file

@ -7,26 +7,15 @@ use axum_extra::{
headers::{Authorization, authorization::Bearer},
};
use log::debug;
use serde::Deserialize;
use super::VaultPath;
use crate::server::{auth::auth, responses::PingResponse};
use crate::{
app_state::{AppState, database::models::VaultId},
consts::SUPPORTED_API_VERSION,
errors::SyncServerError,
utils::normalize_vault_id::normalize_vault_id,
};
#[derive(Deserialize)]
pub struct PingPathParams {
#[serde(deserialize_with = "normalize_vault_id")]
vault_id: VaultId,
}
use crate::{app_state::AppState, consts::SUPPORTED_API_VERSION, errors::SyncServerError};
#[axum::debug_handler]
pub async fn ping(
maybe_auth_header: Option<TypedHeader<Authorization<Bearer>>>,
Path(PingPathParams { vault_id }): Path<PingPathParams>,
Path(VaultPath(vault_id)): Path<VaultPath>,
State(state): State<AppState>,
) -> Result<Json<PingResponse>, SyncServerError> {
debug!("Pinging vault `{vault_id}`");

View file

@ -1,4 +1,4 @@
use super::utils::find_already_processed_event;
use super::{VaultPath, utils::find_already_processed_event};
use crate::{
app_state::{
AppState,
@ -14,7 +14,6 @@ use crate::{
requests::{PushContent, PutFileContent},
responses::DocumentUpdateResponse,
},
utils::normalize_vault_id::normalize_vault_id,
};
use anyhow::anyhow;
use axum::{
@ -25,37 +24,26 @@ use axum_extra::TypedHeader;
use base64::{Engine as _, engine::general_purpose::STANDARD};
use log::debug;
use reconcile_text::{BuiltinTokenizer, EditedText, NumberOrText};
use serde::Deserialize;
use sha2::{Digest, Sha256};
#[derive(Deserialize)]
pub struct PutFileContentPath {
#[serde(deserialize_with = "normalize_vault_id")]
vault_id: VaultId,
document_id: DocumentId,
}
#[axum::debug_handler]
pub async fn put_file_content(
Path(path): Path<PutFileContentPath>,
Path((VaultPath(vault_id), document_id)): Path<(VaultPath, DocumentId)>,
Extension(user): Extension<User>,
TypedHeader(device): TypedHeader<DeviceIdHeader>,
State(state): State<AppState>,
Json(push): Json<PutFileContent>,
) -> Result<Json<DocumentUpdateResponse>, SyncServerError> {
debug!(
"Pushing document `{}` in vault `{}`",
path.document_id, path.vault_id
);
debug!("Pushing document `{}` in vault `{}`", document_id, vault_id);
let fingerprint = Sha256::digest(
serde_json::to_vec(&("content", path.document_id, &push))
serde_json::to_vec(&("content", document_id, &push))
.map_err(|error| server_error(error.into()))?,
);
let mut tx = state
.database
.create_write_transaction(&path.vault_id)
.create_write_transaction(&vault_id)
.await
.map_err(server_error)?;
@ -74,7 +62,7 @@ pub async fn put_file_content(
let parent = state
.database
.get_latest_document_version(&path.vault_id, &path.document_id, Some(&mut tx))
.get_latest_document_version(&vault_id, &document_id, Some(&mut tx))
.await
.map_err(server_error)?;
@ -87,7 +75,7 @@ pub async fn put_file_content(
} else if push.parent_version_id.is_some() {
return Err(not_found_error(anyhow!(
"Document {} does not exist",
path.document_id
document_id
)));
}
@ -123,7 +111,7 @@ pub async fn put_file_content(
vault_update_id: Database::allocate_event(&mut tx, push.request_id, &fingerprint)
.await
.map_err(server_error)?,
document_id: path.document_id,
document_id,
content,
updated_date: chrono::Utc::now(),
user_id: user.name,
@ -152,10 +140,7 @@ pub async fn put_file_content(
.await
.map_err(|error| server_error(error.into()))?;
state
.broadcasts
.notify_about_vault_update(path.vault_id)
.await;
state.broadcasts.notify_about_vault_update(vault_id).await;
Ok(Json(response))
}
@ -185,10 +170,7 @@ mod tests {
push: PutFileContent,
) -> Result<DocumentUpdateResponse, SyncServerError> {
put_file_content(
Path(PutFileContentPath {
vault_id: vault.clone(),
document_id,
}),
Path((VaultPath(vault.clone()), document_id)),
Extension(User {
name: user_id,
token: String::new(),

View file

@ -4,36 +4,28 @@ use axum::{
extract::{Path, State},
};
use log::debug;
use serde::Deserialize;
use sha2::{Digest, Sha256};
use super::utils::find_already_processed_event;
use super::{VaultPath, utils::find_already_processed_event};
use crate::{
app_state::{
AppState,
database::{
Database,
models::{EventRecord, FileManifest, VaultEvent, VaultId},
models::{EventRecord, FileManifest, VaultEvent},
},
},
errors::{SyncServerError, client_error, server_error},
server::{requests::PushFileManifest, responses::FileManifestUpdateResponse},
utils::normalize_vault_id::normalize_vault_id,
};
#[derive(Deserialize)]
pub struct PutFileManifestPath {
#[serde(deserialize_with = "normalize_vault_id")]
vault_id: VaultId,
}
#[axum::debug_handler]
pub async fn put_file_manifest(
Path(path): Path<PutFileManifestPath>,
Path(VaultPath(vault_id)): Path<VaultPath>,
State(state): State<AppState>,
Json(push): Json<PushFileManifest>,
) -> Result<Json<FileManifestUpdateResponse>, SyncServerError> {
debug!("Pushing file manifest for vault `{}`", path.vault_id);
debug!("Pushing file manifest for vault `{vault_id}`");
// BTreeMap canonicalizes file manifest entries for request fingerprinting.
let fingerprint = Sha256::digest(
@ -43,7 +35,7 @@ pub async fn put_file_manifest(
let mut tx = state
.database
.create_write_transaction(&path.vault_id)
.create_write_transaction(&vault_id)
.await
.map_err(server_error)?;
@ -73,7 +65,7 @@ pub async fn put_file_manifest(
for id in push.entries.keys() {
if state
.database
.get_latest_document_version(&path.vault_id, id, Some(&mut tx))
.get_latest_document_version(&vault_id, id, Some(&mut tx))
.await
.map_err(server_error)?
.is_none()
@ -113,10 +105,7 @@ pub async fn put_file_manifest(
.await
.map_err(|error| server_error(error.into()))?;
state
.broadcasts
.notify_about_vault_update(path.vault_id)
.await;
state.broadcasts.notify_about_vault_update(vault_id).await;
Ok(Json(response))
}

View file

@ -3,33 +3,23 @@ use axum::{
extract::{Path, State},
};
use log::debug;
use serde::Deserialize;
use super::VaultPath;
use crate::{
app_state::{
AppState,
database::models::{VaultId, VaultSnapshot},
},
app_state::{AppState, database::models::VaultSnapshot},
errors::{SyncServerError, server_error},
utils::normalize_vault_id::normalize_vault_id,
};
#[derive(Deserialize)]
pub struct VaultSnapshotPath {
#[serde(deserialize_with = "normalize_vault_id")]
vault_id: VaultId,
}
#[axum::debug_handler]
pub async fn vault_snapshot(
Path(path): Path<VaultSnapshotPath>,
Path(VaultPath(vault_id)): Path<VaultPath>,
State(state): State<AppState>,
) -> Result<Json<VaultSnapshot>, SyncServerError> {
debug!("Fetching vault snapshot for vault `{}`", path.vault_id);
debug!("Fetching vault snapshot for vault `{vault_id}`");
state
.database
.vault_snapshot(&path.vault_id)
.vault_snapshot(&vault_id)
.await
.map(Json)
.map_err(server_error)

View file

@ -9,7 +9,6 @@ use crate::{
},
},
errors::{SyncServerError, client_error, server_error},
utils::normalize_vault_id::normalize_vault_id,
};
use axum::{
extract::{
@ -20,29 +19,21 @@ use axum::{
};
use futures::StreamExt;
use log::{debug, info};
use serde::Deserialize;
use std::time::Duration;
use tokio::sync::broadcast::error::RecvError;
#[derive(Deserialize)]
pub struct WebSocketPathParams {
#[serde(deserialize_with = "normalize_vault_id")]
vault_id: VaultId,
}
use super::VaultPath;
#[axum::debug_handler]
pub async fn websocket_handler(
ws: WebSocketUpgrade,
Path(path): Path<WebSocketPathParams>,
Path(VaultPath(vault_id)): Path<VaultPath>,
State(state): State<AppState>,
) -> Result<Response, SyncServerError> {
debug!(
"Upgrading WebSocket connection for vault `{}`",
path.vault_id
);
debug!("Upgrading WebSocket connection for vault `{vault_id}`");
Ok(ws.on_upgrade(move |socket| async move {
if let Err(error) = websocket(state, socket, path.vault_id).await {
if let Err(error) = websocket(state, socket, vault_id).await {
debug!("WebSocket disconnected: {error}");
}
}))