Clean up diff

This commit is contained in:
Andras Schmelczer 2026-05-09 15:16:16 +01:00
parent 3a20a7c2f8
commit 6d40097bcd
11 changed files with 52 additions and 164 deletions

View file

@ -59,6 +59,26 @@ export class DeterministicAgent extends debugging.InMemoryFileSystem {
this.data.settings = { ...initialSettings }; this.data.settings = { ...initialSettings };
} }
private static isCreateDocumentRequest(
input: RequestInfo | URL,
init: RequestInit | undefined
): boolean {
const method =
init?.method ??
(typeof Request !== "undefined" && input instanceof Request
? input.method
: "GET");
if (method.toUpperCase() !== "POST") {
return false;
}
const url =
input instanceof URL
? input
: new URL(typeof input === "string" ? input : input.url);
return /\/documents\/?$/.test(url.pathname);
}
public async init( public async init(
fetchImplementation: typeof globalThis.fetch fetchImplementation: typeof globalThis.fetch
): Promise<void> { ): Promise<void> {
@ -118,7 +138,7 @@ export class DeterministicAgent extends debugging.InMemoryFileSystem {
this.nextCreateResponseDrop === undefined, this.nextCreateResponseDrop === undefined,
`Client ${this.clientId} already has a create response drop armed` `Client ${this.clientId} already has a create response drop armed`
); );
let resolveDropped!: () => void; let resolveDropped: () => void = () => {};
const dropped = new Promise<void>((resolve) => { const dropped = new Promise<void>((resolve) => {
resolveDropped = resolve; resolveDropped = resolve;
}); });
@ -461,23 +481,4 @@ export class DeterministicAgent extends debugging.InMemoryFileSystem {
}; };
} }
private static isCreateDocumentRequest(
input: RequestInfo | URL,
init: RequestInit | undefined
): boolean {
const method =
init?.method ??
(typeof Request !== "undefined" && input instanceof Request
? input.method
: "GET");
if (method.toUpperCase() !== "POST") {
return false;
}
const url =
input instanceof URL
? input
: new URL(typeof input === "string" ? input : input.url);
return /\/documents\/?$/.test(url.pathname);
}
} }

View file

@ -8,11 +8,9 @@ cd sync-server
cargo test export_bindings cargo test export_bindings
cd - cd -
# Both target directories contain only generated bindings — wipe and copy # Wipe and copy generated bindings into the consuming workspace
rm -f frontend/sync-client/src/services/types/*.ts rm -f frontend/sync-client/src/services/types/*.ts
rm -f frontend/history-ui/src/lib/types/*.ts
cp -r sync-server/bindings/* frontend/sync-client/src/services/types/ cp -r sync-server/bindings/* frontend/sync-client/src/services/types/
cp -r sync-server/bindings/* frontend/history-ui/src/lib/types/
cd frontend cd frontend
npm run lint npm run lint

View file

@ -51,7 +51,8 @@ missing_debug_implementations = "warn"
[lints.clippy] [lints.clippy]
await_holding_lock = "warn" await_holding_lock = "warn"
dbg_macro = "warn" dbg_macro = "warn"
empty_enum = "warn" disallowed_macros = { level = "deny", priority = 1 }
empty_enums = "warn"
enum_glob_use = "warn" enum_glob_use = "warn"
exit = "warn" exit = "warn"
filter_map_next = "warn" filter_map_next = "warn"

4
sync-server/clippy.toml Normal file
View file

@ -0,0 +1,4 @@
disallowed-macros = [
{ path = "std::eprintln", reason = "use log::info! or log::warn! instead" },
{ path = "std::println", reason = "use log::info! or log::warn! instead" },
]

View file

@ -11,7 +11,7 @@ use crate::{
AppState, AppState,
database::models::{DocumentId, DocumentVersion, VaultId, VaultUpdateId}, database::models::{DocumentId, DocumentVersion, VaultId, VaultUpdateId},
}, },
errors::{SyncServerError, client_error, not_found_error, server_error}, errors::{SyncServerError, not_found_error, server_error},
utils::normalize::normalize, utils::normalize::normalize,
}; };
@ -52,7 +52,7 @@ pub async fn fetch_document_version(
)?; )?;
if result.document_id != document_id { if result.document_id != document_id {
return Err(client_error(anyhow!( return Err(not_found_error(anyhow!(
"Document with document id `{document_id}` does not have a version with id \ "Document with document id `{document_id}` does not have a version with id \
`{vault_update_id}`", `{vault_update_id}`",
))); )));

View file

@ -11,7 +11,7 @@ use crate::{
AppState, AppState,
database::models::{DocumentId, VaultId, VaultUpdateId}, database::models::{DocumentId, VaultId, VaultUpdateId},
}, },
errors::{SyncServerError, client_error, not_found_error, server_error}, errors::{SyncServerError, not_found_error, server_error},
utils::normalize::normalize, utils::normalize::normalize,
}; };
@ -52,7 +52,7 @@ pub async fn fetch_document_version_content(
)?; )?;
if result.document_id != document_id { if result.document_id != document_id {
return Err(client_error(anyhow!( return Err(not_found_error(anyhow!(
"Document with document id `{document_id}` does not have a version with id \ "Document with document id `{document_id}` does not have a version with id \
`{vault_update_id}`", `{vault_update_id}`",
))); )));

View file

@ -1,51 +0,0 @@
use anyhow::anyhow;
use axum::{
Json,
extract::{Path, State},
};
use log::debug;
use serde::Deserialize;
use crate::{
app_state::{
AppState,
database::models::{DocumentId, DocumentVersion, VaultId},
},
errors::{SyncServerError, not_found_error, server_error},
utils::normalize::normalize,
};
#[derive(Deserialize)]
pub struct FetchLatestDocumentVersionPathParams {
#[serde(deserialize_with = "normalize")]
vault_id: VaultId,
document_id: DocumentId,
}
#[axum::debug_handler]
pub async fn fetch_latest_document_version(
Path(FetchLatestDocumentVersionPathParams {
vault_id,
document_id,
}): Path<FetchLatestDocumentVersionPathParams>,
State(state): State<AppState>,
) -> Result<Json<DocumentVersion>, SyncServerError> {
debug!("Fetching latest document version for document `{document_id}` in vault `{vault_id}`");
let latest_version = state
.database
.get_latest_document(&vault_id, &document_id, None)
.await
.map_err(server_error)?
.map_or_else(
|| {
Err(not_found_error(anyhow!(
"Document with id `{document_id}` not found",
)))
},
Ok,
)?;
Ok(Json(latest_version.into()))
}

View file

@ -1,59 +0,0 @@
use axum::{
Json,
extract::{Path, Query, State},
};
use log::debug;
use serde::Deserialize;
use super::responses::FetchLatestDocumentsResponse;
use crate::{
app_state::{
AppState,
database::models::{VaultId, VaultUpdateId},
},
errors::{SyncServerError, server_error},
utils::normalize::normalize,
};
#[derive(Deserialize)]
pub struct FetchLatestDocumentsPathParams {
#[serde(deserialize_with = "normalize")]
vault_id: VaultId,
}
#[derive(Deserialize)]
pub struct QueryParams {
since_update_id: Option<VaultUpdateId>,
}
#[axum::debug_handler]
pub async fn fetch_latest_documents(
Path(FetchLatestDocumentsPathParams { vault_id }): Path<FetchLatestDocumentsPathParams>,
Query(QueryParams { since_update_id }): Query<QueryParams>,
State(state): State<AppState>,
) -> Result<Json<FetchLatestDocumentsResponse>, SyncServerError> {
debug!("Fetching latest documents in vault `{vault_id}` since update ID `{since_update_id:?}`");
let documents = if let Some(since_update_id) = since_update_id {
state
.database
.get_latest_documents_since(&vault_id, since_update_id, None, None)
.await
.map_err(server_error)
} else {
state
.database
.get_latest_documents(&vault_id, None, None)
.await
.map_err(server_error)
}?;
Ok(Json(FetchLatestDocumentsResponse {
last_update_id: documents
.iter()
.map(|doc| doc.vault_update_id)
.max()
.unwrap_or(since_update_id.unwrap_or(0)),
latest_documents: documents,
}))
}

View file

@ -9,7 +9,7 @@ use axum_extra::{
use log::debug; use log::debug;
use serde::Deserialize; use serde::Deserialize;
use super::{auth::auth, responses::PingResponse}; use super::{auth::authenticate_for_vault, responses::PingResponse};
use crate::{ use crate::{
app_state::{AppState, database::models::VaultId}, app_state::{AppState, database::models::VaultId},
consts::SUPPORTED_API_VERSION, consts::SUPPORTED_API_VERSION,
@ -31,8 +31,9 @@ pub async fn ping(
) -> Result<Json<PingResponse>, SyncServerError> { ) -> Result<Json<PingResponse>, SyncServerError> {
debug!("Pinging vault `{vault_id}`"); debug!("Pinging vault `{vault_id}`");
let is_authenticated = maybe_auth_header let is_authenticated = maybe_auth_header.is_some_and(|auth_header| {
.is_some_and(|auth_header| auth(&state, auth_header.token(), &vault_id).is_ok()); authenticate_for_vault(&state, auth_header.token(), &vault_id).is_ok()
});
Ok(Json(PingResponse { Ok(Json(PingResponse {
server_version: env!("CARGO_PKG_VERSION").to_owned(), server_version: env!("CARGO_PKG_VERSION").to_owned(),

View file

@ -1,9 +1,7 @@
use serde::{self, Serialize}; use serde::{self, Serialize};
use ts_rs::TS; use ts_rs::TS;
use crate::app_state::database::models::{ use crate::app_state::database::models::{DocumentVersion, DocumentVersionWithoutContent};
DocumentVersion, DocumentVersionWithoutContent, VaultUpdateId,
};
/// Response to a ping request. /// Response to a ping request.
#[derive(TS, Debug, Clone, Serialize)] #[derive(TS, Debug, Clone, Serialize)]
@ -25,17 +23,6 @@ pub struct PingResponse {
pub supported_api_version: u32, pub supported_api_version: u32,
} }
/// Response to a fetch latest documents request.
#[derive(TS, Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
#[ts(export)]
pub struct FetchLatestDocumentsResponse {
pub latest_documents: Vec<DocumentVersionWithoutContent>,
/// The update ID of the latest document in the response.
pub last_update_id: VaultUpdateId,
}
/// Response to a create/update document request. /// Response to a create/update document request.
#[derive(TS, Debug, Clone, Serialize)] #[derive(TS, Debug, Clone, Serialize)]
#[serde(tag = "type")] #[serde(tag = "type")]

View file

@ -2,11 +2,12 @@ use std::{
fs::{self, OpenOptions}, fs::{self, OpenOptions},
io::{self, Write}, io::{self, Write},
path::{Path, PathBuf}, path::{Path, PathBuf},
sync::{Arc, Mutex}, sync::{Arc, Mutex, MutexGuard},
time::{Duration, SystemTime, UNIX_EPOCH}, time::{Duration, SystemTime, UNIX_EPOCH},
}; };
use chrono::NaiveDateTime; use chrono::NaiveDateTime;
use log::warn;
use tracing_subscriber::fmt::MakeWriter; use tracing_subscriber::fmt::MakeWriter;
#[derive(Clone)] #[derive(Clone)]
@ -93,6 +94,17 @@ impl RotatingFileWriter {
SystemTime::now() >= inner.next_rotation_time SystemTime::now() >= inner.next_rotation_time
} }
fn lock_inner(&self) -> MutexGuard<'_, RotatingFileWriterInner> {
match self.inner.lock() {
Ok(inner) => inner,
Err(poisoned) => {
warn!("RotatingFileWriter mutex was poisoned, recovering");
self.inner.clear_poison();
poisoned.into_inner()
}
}
}
fn open_or_create_log_file(inner: &mut RotatingFileWriterInner) -> io::Result<()> { fn open_or_create_log_file(inner: &mut RotatingFileWriterInner) -> io::Result<()> {
// If we haven't reached rotation time and there's an existing log file, reuse it // If we haven't reached rotation time and there's an existing log file, reuse it
if !Self::should_rotate(inner) if !Self::should_rotate(inner)
@ -132,10 +144,7 @@ impl RotatingFileWriter {
impl Write for RotatingFileWriter { impl Write for RotatingFileWriter {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> { fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let mut inner = self.inner.lock().unwrap_or_else(|poisoned| { let mut inner = self.lock_inner();
eprintln!("RotatingFileWriter mutex was poisoned, recovering");
poisoned.into_inner()
});
// Reset file handle after poison recovery so the next branch // Reset file handle after poison recovery so the next branch
// re-opens a valid file rather than writing to a potentially // re-opens a valid file rather than writing to a potentially
@ -154,10 +163,7 @@ impl Write for RotatingFileWriter {
} }
fn flush(&mut self) -> io::Result<()> { fn flush(&mut self) -> io::Result<()> {
let mut inner = self.inner.lock().unwrap_or_else(|poisoned| { let mut inner = self.lock_inner();
eprintln!("RotatingFileWriter mutex was poisoned, recovering");
poisoned.into_inner()
});
if let Some(ref mut file) = inner.current_file { if let Some(ref mut file) = inner.current_file {
file.flush() file.flush()
} else { } else {