diff --git a/frontend/deterministic-tests/src/deterministic-agent.ts b/frontend/deterministic-tests/src/deterministic-agent.ts index b32b01c2..9fb1eaa5 100644 --- a/frontend/deterministic-tests/src/deterministic-agent.ts +++ b/frontend/deterministic-tests/src/deterministic-agent.ts @@ -59,6 +59,26 @@ export class DeterministicAgent extends debugging.InMemoryFileSystem { 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( fetchImplementation: typeof globalThis.fetch ): Promise { @@ -118,7 +138,7 @@ export class DeterministicAgent extends debugging.InMemoryFileSystem { this.nextCreateResponseDrop === undefined, `Client ${this.clientId} already has a create response drop armed` ); - let resolveDropped!: () => void; + let resolveDropped: () => void = () => {}; const dropped = new Promise((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); - } } diff --git a/scripts/update-api-types.sh b/scripts/update-api-types.sh index 3f4a9e2a..5c49f10d 100755 --- a/scripts/update-api-types.sh +++ b/scripts/update-api-types.sh @@ -8,11 +8,9 @@ cd sync-server cargo test export_bindings 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/history-ui/src/lib/types/*.ts cp -r sync-server/bindings/* frontend/sync-client/src/services/types/ -cp -r sync-server/bindings/* frontend/history-ui/src/lib/types/ cd frontend npm run lint diff --git a/sync-server/Cargo.toml b/sync-server/Cargo.toml index 2fed9d9b..c51460eb 100644 --- a/sync-server/Cargo.toml +++ b/sync-server/Cargo.toml @@ -51,7 +51,8 @@ missing_debug_implementations = "warn" [lints.clippy] await_holding_lock = "warn" dbg_macro = "warn" -empty_enum = "warn" +disallowed_macros = { level = "deny", priority = 1 } +empty_enums = "warn" enum_glob_use = "warn" exit = "warn" filter_map_next = "warn" diff --git a/sync-server/clippy.toml b/sync-server/clippy.toml new file mode 100644 index 00000000..2b275dbd --- /dev/null +++ b/sync-server/clippy.toml @@ -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" }, +] diff --git a/sync-server/src/server/fetch_document_version.rs b/sync-server/src/server/fetch_document_version.rs index 159cad3a..c30f1d76 100644 --- a/sync-server/src/server/fetch_document_version.rs +++ b/sync-server/src/server/fetch_document_version.rs @@ -11,7 +11,7 @@ use crate::{ AppState, 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, }; @@ -52,7 +52,7 @@ pub async fn fetch_document_version( )?; 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 \ `{vault_update_id}`", ))); diff --git a/sync-server/src/server/fetch_document_version_content.rs b/sync-server/src/server/fetch_document_version_content.rs index a163b036..9fdd0ad8 100644 --- a/sync-server/src/server/fetch_document_version_content.rs +++ b/sync-server/src/server/fetch_document_version_content.rs @@ -11,7 +11,7 @@ use crate::{ AppState, database::models::{DocumentId, VaultId, VaultUpdateId}, }, - errors::{SyncServerError, client_error, not_found_error, server_error}, + errors::{SyncServerError, not_found_error, server_error}, utils::normalize::normalize, }; @@ -52,7 +52,7 @@ pub async fn fetch_document_version_content( )?; 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 \ `{vault_update_id}`", ))); diff --git a/sync-server/src/server/fetch_latest_document_version.rs b/sync-server/src/server/fetch_latest_document_version.rs deleted file mode 100644 index a9973606..00000000 --- a/sync-server/src/server/fetch_latest_document_version.rs +++ /dev/null @@ -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, - State(state): State, -) -> Result, 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())) -} diff --git a/sync-server/src/server/fetch_latest_documents.rs b/sync-server/src/server/fetch_latest_documents.rs deleted file mode 100644 index f1ca702d..00000000 --- a/sync-server/src/server/fetch_latest_documents.rs +++ /dev/null @@ -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, -} - -#[axum::debug_handler] -pub async fn fetch_latest_documents( - Path(FetchLatestDocumentsPathParams { vault_id }): Path, - Query(QueryParams { since_update_id }): Query, - State(state): State, -) -> Result, 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, - })) -} diff --git a/sync-server/src/server/ping.rs b/sync-server/src/server/ping.rs index 31aa8acd..6740acae 100644 --- a/sync-server/src/server/ping.rs +++ b/sync-server/src/server/ping.rs @@ -9,7 +9,7 @@ use axum_extra::{ use log::debug; use serde::Deserialize; -use super::{auth::auth, responses::PingResponse}; +use super::{auth::authenticate_for_vault, responses::PingResponse}; use crate::{ app_state::{AppState, database::models::VaultId}, consts::SUPPORTED_API_VERSION, @@ -31,8 +31,9 @@ pub async fn ping( ) -> Result, SyncServerError> { debug!("Pinging vault `{vault_id}`"); - let is_authenticated = maybe_auth_header - .is_some_and(|auth_header| auth(&state, auth_header.token(), &vault_id).is_ok()); + let is_authenticated = maybe_auth_header.is_some_and(|auth_header| { + authenticate_for_vault(&state, auth_header.token(), &vault_id).is_ok() + }); Ok(Json(PingResponse { server_version: env!("CARGO_PKG_VERSION").to_owned(), diff --git a/sync-server/src/server/responses.rs b/sync-server/src/server/responses.rs index 47b6e402..c07c054b 100644 --- a/sync-server/src/server/responses.rs +++ b/sync-server/src/server/responses.rs @@ -1,9 +1,7 @@ use serde::{self, Serialize}; use ts_rs::TS; -use crate::app_state::database::models::{ - DocumentVersion, DocumentVersionWithoutContent, VaultUpdateId, -}; +use crate::app_state::database::models::{DocumentVersion, DocumentVersionWithoutContent}; /// Response to a ping request. #[derive(TS, Debug, Clone, Serialize)] @@ -25,17 +23,6 @@ pub struct PingResponse { 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, - - /// The update ID of the latest document in the response. - pub last_update_id: VaultUpdateId, -} - /// Response to a create/update document request. #[derive(TS, Debug, Clone, Serialize)] #[serde(tag = "type")] diff --git a/sync-server/src/utils/rotating_file_writer.rs b/sync-server/src/utils/rotating_file_writer.rs index 1c5c86c5..da6d0d7d 100644 --- a/sync-server/src/utils/rotating_file_writer.rs +++ b/sync-server/src/utils/rotating_file_writer.rs @@ -2,11 +2,12 @@ use std::{ fs::{self, OpenOptions}, io::{self, Write}, path::{Path, PathBuf}, - sync::{Arc, Mutex}, + sync::{Arc, Mutex, MutexGuard}, time::{Duration, SystemTime, UNIX_EPOCH}, }; use chrono::NaiveDateTime; +use log::warn; use tracing_subscriber::fmt::MakeWriter; #[derive(Clone)] @@ -93,6 +94,17 @@ impl RotatingFileWriter { 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<()> { // If we haven't reached rotation time and there's an existing log file, reuse it if !Self::should_rotate(inner) @@ -132,10 +144,7 @@ impl RotatingFileWriter { impl Write for RotatingFileWriter { fn write(&mut self, buf: &[u8]) -> io::Result { - let mut inner = self.inner.lock().unwrap_or_else(|poisoned| { - eprintln!("RotatingFileWriter mutex was poisoned, recovering"); - poisoned.into_inner() - }); + let mut inner = self.lock_inner(); // Reset file handle after poison recovery so the next branch // 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<()> { - let mut inner = self.inner.lock().unwrap_or_else(|poisoned| { - eprintln!("RotatingFileWriter mutex was poisoned, recovering"); - poisoned.into_inner() - }); + let mut inner = self.lock_inner(); if let Some(ref mut file) = inner.current_file { file.flush() } else {