Remove aide

This commit is contained in:
Andras Schmelczer 2025-06-07 17:15:16 +01:00
parent 0908a5b527
commit 4ca55768c5
No known key found for this signature in database
GPG key ID: FC8F2C3D3D1A718C
15 changed files with 85 additions and 562 deletions

286
backend/Cargo.lock generated
View file

@ -17,20 +17,6 @@ version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
[[package]]
name = "ahash"
version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
dependencies = [
"cfg-if",
"getrandom 0.2.15",
"once_cell",
"serde",
"version_check",
"zerocopy 0.7.35",
]
[[package]]
name = "aho-corasick"
version = "1.1.3"
@ -40,41 +26,6 @@ dependencies = [
"memchr",
]
[[package]]
name = "aide"
version = "0.13.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5678d2978845ddb4bd736a026f467dd652d831e9e6254b0e41b07f7ee7523309"
dependencies = [
"axum",
"axum-extra",
"bytes",
"cfg-if",
"http",
"indexmap 2.7.0",
"schemars",
"serde",
"serde_json",
"serde_qs",
"thiserror 1.0.69",
"tower-layer",
"tower-service",
"tracing",
]
[[package]]
name = "aide-axum-typed-multipart"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b8f5c830a08754addfa31fa09e6c183bac8d2ae7bd007131f9eb84fcb87a40e"
dependencies = [
"aide",
"axum",
"axum_typed_multipart",
"indexmap 2.7.0",
"schemars",
]
[[package]]
name = "allocator-api2"
version = "0.2.21"
@ -265,26 +216,6 @@ dependencies = [
"tower-service",
]
[[package]]
name = "axum-jsonschema"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcffe29ca1b60172349fea781ec34441d598809bd227ccbb5bf5dc2879cd9c78"
dependencies = [
"aide",
"async-trait",
"axum",
"http",
"http-body",
"itertools",
"jsonschema",
"schemars",
"serde",
"serde_json",
"serde_path_to_error",
"tracing",
]
[[package]]
name = "axum-macros"
version = "0.4.2"
@ -368,21 +299,6 @@ version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "230c5f1ca6a325a32553f8640d31ac9b49f2411e901e427570154868b46da4f7"
[[package]]
name = "bit-set"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1"
dependencies = [
"bit-vec",
]
[[package]]
name = "bit-vec"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
[[package]]
name = "bitflags"
version = "2.6.0"
@ -407,12 +323,6 @@ version = "3.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
[[package]]
name = "bytecount"
version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ce89b21cab1437276d2650d57e971f9d548a2d9037cc231abdc0562b97498ce"
[[package]]
name = "byteorder"
version = "1.5.0"
@ -700,12 +610,6 @@ version = "0.15.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
[[package]]
name = "dyn-clone"
version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125"
[[package]]
name = "either"
version = "1.13.0"
@ -768,16 +672,6 @@ dependencies = [
"pin-project-lite",
]
[[package]]
name = "fancy-regex"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b95f7c0680e4142284cf8b22c14a476e87d61b004a3a0861872b32ef7ead40a2"
dependencies = [
"bit-set",
"regex",
]
[[package]]
name = "fastrand"
version = "2.2.0"
@ -816,16 +710,6 @@ dependencies = [
"percent-encoding",
]
[[package]]
name = "fraction"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3027ae1df8d41b4bed2241c8fdad4acc1e7af60c8e17743534b545e77182d678"
dependencies = [
"lazy_static",
"num",
]
[[package]]
name = "futures"
version = "0.3.31"
@ -943,10 +827,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
dependencies = [
"cfg-if",
"js-sys",
"libc",
"wasi 0.11.0+wasi-snapshot-preview1",
"wasm-bindgen",
]
[[package]]
@ -1346,24 +1228,6 @@ version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]]
name = "iso8601"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "924e5d73ea28f59011fec52a0d12185d496a9b075d360657aed2a5707f701153"
dependencies = [
"nom",
]
[[package]]
name = "itertools"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
dependencies = [
"either",
]
[[package]]
name = "itoa"
version = "1.0.14"
@ -1380,34 +1244,6 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "jsonschema"
version = "0.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a071f4f7efc9a9118dfb627a0a94ef247986e1ab8606a4c806ae2b3aa3b6978"
dependencies = [
"ahash",
"anyhow",
"base64 0.21.7",
"bytecount",
"fancy-regex",
"fraction",
"getrandom 0.2.15",
"iso8601",
"itoa",
"memchr",
"num-cmp",
"once_cell",
"parking_lot",
"percent-encoding",
"regex",
"serde",
"serde_json",
"time",
"url",
"uuid",
]
[[package]]
name = "lazy_static"
version = "1.5.0"
@ -1521,12 +1357,6 @@ dependencies = [
"walkdir",
]
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "miniz_oxide"
version = "0.8.0"
@ -1564,16 +1394,6 @@ dependencies = [
"version_check",
]
[[package]]
name = "nom"
version = "7.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
dependencies = [
"memchr",
"minimal-lexical",
]
[[package]]
name = "nu-ansi-term"
version = "0.46.0"
@ -1584,30 +1404,6 @@ dependencies = [
"winapi",
]
[[package]]
name = "num"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23"
dependencies = [
"num-bigint",
"num-complex",
"num-integer",
"num-iter",
"num-rational",
"num-traits",
]
[[package]]
name = "num-bigint"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
dependencies = [
"num-integer",
"num-traits",
]
[[package]]
name = "num-bigint-dig"
version = "0.8.4"
@ -1625,21 +1421,6 @@ dependencies = [
"zeroize",
]
[[package]]
name = "num-cmp"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63335b2e2c34fae2fb0aa2cecfd9f0832a1e24b3b32ecec612c3426d46dc8aaa"
[[package]]
name = "num-complex"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495"
dependencies = [
"num-traits",
]
[[package]]
name = "num-conv"
version = "0.1.0"
@ -1666,17 +1447,6 @@ dependencies = [
"num-traits",
]
[[package]]
name = "num-rational"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824"
dependencies = [
"num-bigint",
"num-integer",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.19"
@ -2077,34 +1847,6 @@ dependencies = [
"regex",
]
[[package]]
name = "schemars"
version = "0.8.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615"
dependencies = [
"bytes",
"chrono",
"dyn-clone",
"indexmap 2.7.0",
"schemars_derive",
"serde",
"serde_json",
"uuid",
]
[[package]]
name = "schemars_derive"
version = "0.8.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d"
dependencies = [
"proc-macro2",
"quote",
"serde_derive_internals",
"syn 2.0.90",
]
[[package]]
name = "scoped-tls"
version = "1.0.1"
@ -2137,17 +1879,6 @@ dependencies = [
"syn 2.0.90",
]
[[package]]
name = "serde_derive_internals"
version = "0.29.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.90",
]
[[package]]
name = "serde_json"
version = "1.0.140"
@ -2170,19 +1901,6 @@ dependencies = [
"serde",
]
[[package]]
name = "serde_qs"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd34f36fe4c5ba9654417139a9b3a20d2e1de6012ee678ad14d240c22c78d8d6"
dependencies = [
"axum",
"futures",
"percent-encoding",
"serde",
"thiserror 1.0.69",
]
[[package]]
name = "serde_urlencoded"
version = "0.7.1"
@ -2610,12 +2328,9 @@ dependencies = [
name = "sync_server"
version = "0.3.15"
dependencies = [
"aide",
"aide-axum-typed-multipart",
"anyhow",
"axum",
"axum-extra",
"axum-jsonschema",
"axum_typed_multipart",
"bimap",
"chrono",
@ -2626,7 +2341,6 @@ dependencies = [
"rand 0.9.0",
"regex",
"sanitize-filename",
"schemars",
"serde",
"serde_json",
"serde_with",

View file

@ -18,22 +18,18 @@ log = { version = "0.4.27" }
anyhow = { version = "1.0.98", features = ["backtrace"] }
axum = { version = "0.7.4", features = ["ws", "macros", "tracing", "multipart"]}
axum-extra = { version = "0.9.6", features = ["typed-header"] }
aide-axum-typed-multipart = "0.13.0"
axum_typed_multipart = "0.11.0"
tower-http = { version = "0.6.1", features = ["cors", "trace", "limit", "timeout"] }
tracing = "0.1.41"
tracing-subscriber = { version = "0.3.19", features = ["fmt", "env-filter"]}
serde_yaml = "0.9.34"
sqlx = { version = "0.8.6", features = ["sqlite", "runtime-tokio", "uuid", "chrono"] }
chrono = { version = "0.4.41", features = ["serde"] }
aide = { version = "0.13.5", features = ["axum", "axum-ws", "scalar", "axum-headers"] }
schemars = { version = "0.8.22", features = ["chrono", "uuid1", "bytes"] }
tracing = "0.1.41"
rand = "0.9.0"
sanitize-filename = "0.6.0"
axum-jsonschema = { version = "0.8.0", features = ["aide"] }
regex = "1.11.1"
clap = { version = "4.5.38", features = ["derive"] }
futures = "0.3.31"
serde_yaml = "0.9.34"
serde_json = "1.0.140"
clap-verbosity-flag = "3.0.3"
bimap = "0.6.3"

View file

@ -1,5 +1,4 @@
use chrono::{DateTime, Utc};
use schemars::JsonSchema;
use serde::Serialize;
use sync_lib::bytes_to_base64;
use ts_rs::TS;
@ -27,7 +26,7 @@ impl PartialEq<Self> for StoredDocumentVersion {
fn eq(&self, other: &Self) -> bool { self.vault_update_id == other.vault_update_id }
}
#[derive(TS, Debug, Clone, Serialize, JsonSchema)]
#[derive(TS, Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct DocumentVersionWithoutContent {
#[ts(as = "i32")]
@ -59,7 +58,7 @@ impl From<StoredDocumentVersion> for DocumentVersionWithoutContent {
}
}
#[derive(Debug, Clone, Serialize, JsonSchema)]
#[derive(TS, Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct DocumentVersion {
pub vault_update_id: VaultUpdateId,

View file

@ -12,29 +12,20 @@ mod responses;
mod update_document;
mod websocket;
use std::{ffi::OsString, sync::Arc, time::Duration};
use std::{ffi::OsString, time::Duration};
use aide::{
axum::{
ApiRouter,
routing::{delete, get, post, put},
},
openapi::{Info, OpenApi},
scalar::Scalar,
transform::TransformOpenApi,
};
use anyhow::{Context as _, Result, anyhow};
use auth::auth_middleware;
use axum::{
Extension, Json,
Router,
extract::{DefaultBodyLimit, Request},
http::{self, HeaderValue, Method},
middleware,
response::IntoResponse,
routing::IntoMakeService,
routing::{IntoMakeService, delete, get, post, put},
};
use device_id_header::DEVICE_ID_HEADER_NAME;
use log::{error, info};
use log::info;
use tokio::signal;
use tower_http::{
LatencyUnit,
@ -51,26 +42,20 @@ use tracing::{Level, info_span};
use crate::{
app_state::AppState,
config::server_config::ServerConfig,
errors::{SerializedError, client_error, not_found_error},
errors::{client_error, not_found_error},
};
pub async fn create_server(config_path: Option<OsString>) -> Result<()> {
aide::r#gen::on_error(|err| error!("{err}"));
aide::r#gen::extract_schemas(true);
let app_state = AppState::try_new(config_path)
.await
.context("Failed to initialise app state")?;
let server_config = app_state.config.server.clone();
let mut api = create_open_api();
let app = ApiRouter::new()
let app = Router::new()
.nest("/", get_authed_routes(app_state.clone()))
.api_route("/vaults/:vault_id/ping", get(ping::ping))
.route("/vaults/:vault_id/ping", get(ping::ping))
.route("/vaults/:vault_id/ws", get(websocket::websocket_handler))
.route("/", Scalar::new("/api.json").axum_route())
.route("/api.json", axum::routing::get(serve_api))
.layer(DefaultBodyLimit::disable())
.layer(RequestBodyLimitLayer::new(
app_state.config.server.max_body_size_mb * 1024 * 1024,
@ -108,8 +93,6 @@ pub async fn create_server(config_path: Option<OsString>) -> Result<()> {
.on_failure(DefaultOnFailure::new().level(Level::ERROR)),
)
.with_state(app_state)
.finish_api_with(&mut api, add_api_docs_error_example)
.layer(Extension(Arc::new(api))) // https://github.com/tamasfe/aide/blob/507f4a8822bc0c13cbda0f589da1e0f4cbcdb812/examples/example-axum/src/main.rs#L39
.fallback(handle_404)
.fallback(handle_405)
.into_make_service();
@ -117,67 +100,33 @@ pub async fn create_server(config_path: Option<OsString>) -> Result<()> {
start_server(app, &server_config).await
}
async fn serve_api(Extension(api): Extension<Arc<OpenApi>>) -> impl IntoResponse { Json(api) }
fn create_open_api() -> OpenApi {
OpenApi {
info: Info {
title: "VaultLink sync server".to_owned(),
summary: Some(
"Simple API for syncing documents between concurrent clients.".to_owned(),
),
description: Some(include_str!("../README.md").to_owned()),
version: env!("CARGO_PKG_VERSION").to_owned(),
..Info::default()
},
..OpenApi::default()
}
}
fn add_api_docs_error_example(api: TransformOpenApi<'_>) -> TransformOpenApi<'_> {
api.default_response_with::<Json<SerializedError>, _>(|res| {
res.example(SerializedError {
message: "An error has occurred".to_owned(),
causes: vec![],
})
})
}
fn get_authed_routes(app_state: AppState) -> ApiRouter<AppState> {
ApiRouter::new()
.api_route(
fn get_authed_routes(app_state: AppState) -> Router<AppState> {
Router::new()
.route(
"/vaults/:vault_id/documents",
get(fetch_latest_documents::fetch_latest_documents),
)
.api_route(
.route(
"/vaults/:vault_id/documents",
post(create_document::create_document_multipart),
post(create_document::create_document),
)
.api_route(
"/vaults/:vault_id/documents/json",
post(create_document::create_document_json),
)
.api_route(
.route(
"/vaults/:vault_id/documents/:document_id",
get(fetch_latest_document_version::fetch_latest_document_version),
)
.api_route(
.route(
"/vaults/:vault_id/documents/:document_id",
put(update_document::update_document_multipart),
put(update_document::update_document),
)
.api_route(
"/vaults/:vault_id/documents/:document_id/json",
put(update_document::update_document_json),
)
.api_route(
.route(
"/vaults/:vault_id/documents/:document_id/versions/:version_id",
put(fetch_document_version::fetch_document_version),
)
.api_route(
.route(
"/vaults/:vault_id/documents/:document_id/versions/:version_id/content",
put(fetch_document_version_content::fetch_document_version_content),
)
.api_route(
.route(
"/vaults/:vault_id/documents/:document_id",
delete(delete_document::delete_document),
)

View file

@ -1,33 +1,24 @@
use aide_axum_typed_multipart::TypedMultipart;
use anyhow::Context as _;
use axum::{
Extension,
Extension, Json,
extract::{Path, State},
};
use axum_extra::TypedHeader;
use axum_jsonschema::Json;
use schemars::JsonSchema;
use axum_typed_multipart::TypedMultipart;
use serde::Deserialize;
use sync_lib::base64_to_bytes;
use super::{
device_id_header::DeviceIdHeader,
requests::{CreateDocumentVersion, CreateDocumentVersionMultipart},
};
use super::{device_id_header::DeviceIdHeader, requests::CreateDocumentVersion};
use crate::{
app_state::{
AppState,
database::models::{
DocumentId, DocumentVersionWithoutContent, StoredDocumentVersion, VaultId,
},
database::models::{DocumentVersionWithoutContent, StoredDocumentVersion, VaultId},
},
config::user_config::User,
errors::{SyncServerError, client_error, server_error},
utils::{normalize::normalize, sanitize_path::sanitize_path},
};
// This is required for aide to infer the path parameter types and names
#[derive(Deserialize, JsonSchema)]
#[derive(Deserialize)]
pub struct CreateDocumentPathParams {
#[serde(deserialize_with = "normalize")]
vault_id: VaultId,
@ -37,63 +28,12 @@ pub struct CreateDocumentPathParams {
/// already. If a document with the same path exists, a new version is created
/// with their content merged.
#[axum::debug_handler]
pub async fn create_document_multipart(
pub async fn create_document(
Path(CreateDocumentPathParams { vault_id }): Path<CreateDocumentPathParams>,
Extension(user): Extension<User>,
TypedHeader(device_id): TypedHeader<DeviceIdHeader>,
State(state): State<AppState>,
TypedMultipart(axum_typed_multipart::TypedMultipart(request)): TypedMultipart<
CreateDocumentVersionMultipart,
>,
) -> Result<Json<DocumentVersionWithoutContent>, SyncServerError> {
internal_create_document(
user,
device_id,
state,
vault_id,
request.document_id,
request.relative_path,
request.content.contents.to_vec(),
)
.await
}
/// 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]
pub async fn create_document_json(
Path(CreateDocumentPathParams { vault_id }): Path<CreateDocumentPathParams>,
Extension(user): Extension<User>,
TypedHeader(device_id): TypedHeader<DeviceIdHeader>,
State(state): State<AppState>,
Json(request): Json<CreateDocumentVersion>,
) -> Result<Json<DocumentVersionWithoutContent>, SyncServerError> {
let content_bytes = base64_to_bytes(&request.content_base64)
.context("Failed to decode base64 content in request")
.map_err(client_error)?;
internal_create_document(
user,
device_id,
state,
vault_id,
request.document_id,
request.relative_path,
content_bytes,
)
.await
}
#[allow(clippy::too_many_arguments)]
async fn internal_create_document(
user: User,
device_id: DeviceIdHeader,
state: AppState,
vault_id: VaultId,
document_id: Option<DocumentId>,
relative_path: String,
content: Vec<u8>,
TypedMultipart(request): TypedMultipart<CreateDocumentVersion>,
) -> Result<Json<DocumentVersionWithoutContent>, SyncServerError> {
let mut transaction = state
.database
@ -101,7 +41,7 @@ async fn internal_create_document(
.await
.map_err(server_error)?;
let document_id = match document_id {
let document_id = match request.document_id {
Some(document_id) => {
let existing_version = state
.database
@ -126,13 +66,13 @@ async fn internal_create_document(
.await
.map_err(server_error)?;
let sanitized_relative_path = sanitize_path(&relative_path);
let sanitized_relative_path = sanitize_path(&request.relative_path);
let new_version = StoredDocumentVersion {
vault_update_id: last_update_id + 1,
document_id,
relative_path: sanitized_relative_path,
content,
content: request.content.contents.to_vec(),
updated_date: chrono::Utc::now(),
is_deleted: false,
user_id: user.name,

View file

@ -1,11 +1,9 @@
use anyhow::Context as _;
use axum::{
Extension,
Extension, Json,
extract::{Path, State},
};
use axum_extra::TypedHeader;
use axum_jsonschema::Json;
use schemars::JsonSchema;
use serde::Deserialize;
use super::{device_id_header::DeviceIdHeader, requests::DeleteDocumentVersion};
@ -21,8 +19,7 @@ use crate::{
utils::{normalize::normalize, sanitize_path::sanitize_path},
};
// This is required for aide to infer the path parameter types and names
#[derive(Deserialize, JsonSchema)]
#[derive(Deserialize)]
pub struct DeleteDocumentPathParams {
#[serde(deserialize_with = "normalize")]
vault_id: VaultId,

View file

@ -1,7 +1,8 @@
use anyhow::anyhow;
use axum::extract::{Path, State};
use axum_jsonschema::Json;
use schemars::JsonSchema;
use axum::{
Json,
extract::{Path, State},
};
use serde::Deserialize;
use crate::{
@ -13,8 +14,7 @@ use crate::{
utils::normalize::normalize,
};
// This is required for aide to infer the path parameter types and names
#[derive(Deserialize, JsonSchema)]
#[derive(Deserialize)]
pub struct FetchDocumentVersionPathParams {
#[serde(deserialize_with = "normalize")]
vault_id: VaultId,

View file

@ -3,7 +3,6 @@ use axum::{
body::Bytes,
extract::{Path, State},
};
use schemars::JsonSchema;
use serde::Deserialize;
use crate::{
@ -15,8 +14,7 @@ use crate::{
utils::normalize::normalize,
};
// This is required for aide to infer the path parameter types and names
#[derive(Deserialize, JsonSchema)]
#[derive(Deserialize)]
pub struct FetchDocumentVersionContentPathParams {
#[serde(deserialize_with = "normalize")]
vault_id: VaultId,

View file

@ -1,7 +1,8 @@
use anyhow::anyhow;
use axum::extract::{Path, State};
use axum_jsonschema::Json;
use schemars::JsonSchema;
use axum::{
Json,
extract::{Path, State},
};
use serde::Deserialize;
use crate::{
@ -13,8 +14,7 @@ use crate::{
utils::normalize::normalize,
};
// This is required for aide to infer the path parameter types and names
#[derive(Deserialize, JsonSchema)]
#[derive(Deserialize)]
pub struct FetchLatestDocumentVersionPathParams {
#[serde(deserialize_with = "normalize")]
vault_id: VaultId,

View file

@ -1,6 +1,7 @@
use axum::extract::{Path, Query, State};
use axum_jsonschema::Json;
use schemars::JsonSchema;
use axum::{
Json,
extract::{Path, Query, State},
};
use serde::Deserialize;
use super::responses::FetchLatestDocumentsResponse;
@ -13,15 +14,13 @@ use crate::{
utils::normalize::normalize,
};
// This is required for aide to infer the path parameter types and names
#[derive(Deserialize, JsonSchema)]
#[derive(Deserialize)]
pub struct FetchLatestDocumentsPathParams {
#[serde(deserialize_with = "normalize")]
vault_id: VaultId,
}
// This is required for aide to infer the path parameter types and names
#[derive(Deserialize, JsonSchema)]
#[derive(Deserialize)]
pub struct QueryParams {
since_update_id: Option<VaultUpdateId>,
}

View file

@ -6,7 +6,6 @@ use axum_extra::{
TypedHeader,
headers::{Authorization, authorization::Bearer},
};
use schemars::JsonSchema;
use serde::Deserialize;
use super::{auth::auth, responses::PingResponse};
@ -16,8 +15,7 @@ use crate::{
utils::normalize::normalize,
};
// This is required for aide to infer the path parameter types and names
#[derive(Deserialize, JsonSchema)]
#[derive(Deserialize)]
pub struct PingPathParams {
#[serde(deserialize_with = "normalize")]
vault_id: VaultId,

View file

@ -1,13 +1,12 @@
use aide_axum_typed_multipart::FieldData;
use axum::body::Bytes;
use axum_typed_multipart::TryFromMultipart;
use schemars::JsonSchema;
use axum_typed_multipart::{FieldData, TryFromMultipart};
use serde::{self, Deserialize};
use ts_rs::TS;
use crate::app_state::database::models::{DocumentId, VaultUpdateId};
#[derive(Debug, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
#[derive(TS, Debug, TryFromMultipart)]
#[ts(export)]
pub struct CreateDocumentVersion {
/// The client can decide the document id (if it wishes to) in order
/// to help with syncing. If the client does not provide a document id,
@ -15,36 +14,26 @@ pub struct CreateDocumentVersion {
/// it must not already exist in the database.
pub document_id: Option<DocumentId>,
pub relative_path: String,
pub content_base64: String,
}
#[derive(Debug, TryFromMultipart, JsonSchema)]
pub struct CreateDocumentVersionMultipart {
pub document_id: Option<DocumentId>,
pub relative_path: String,
#[ts(as = "Vec<u8>")]
#[form_data(limit = "unlimited")]
pub content: FieldData<Bytes>,
}
#[derive(Debug, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
#[derive(TS, Debug, TryFromMultipart)]
#[ts(export)]
pub struct UpdateDocumentVersion {
pub parent_version_id: VaultUpdateId,
pub relative_path: String,
pub content_base64: String,
}
#[derive(Debug, TryFromMultipart, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct UpdateDocumentVersionMultipart {
pub parent_version_id: VaultUpdateId,
pub relative_path: String,
#[ts(as = "Vec<u8>")]
#[form_data(limit = "unlimited")]
pub content: FieldData<Bytes>,
}
#[derive(Debug, Deserialize, JsonSchema)]
#[derive(TS, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
#[ts(export)]
pub struct DeleteDocumentVersion {
pub relative_path: String,
}

View file

@ -1,13 +1,14 @@
use schemars::JsonSchema;
use serde::{self, Serialize};
use ts_rs::TS;
use crate::app_state::database::models::{
DocumentVersion, DocumentVersionWithoutContent, VaultUpdateId,
};
/// Response to a ping request.
#[derive(Debug, Clone, Serialize, JsonSchema)]
#[derive(TS, Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
#[ts(export)]
pub struct PingResponse {
/// Semantic version of the server.
pub server_version: String,
@ -18,8 +19,9 @@ pub struct PingResponse {
}
/// Response to a fetch latest documents request.
#[derive(Debug, Clone, Serialize, JsonSchema)]
#[derive(TS, Debug, Clone, Serialize)]
#[serde(rename_all = "camelCase")]
#[ts(export)]
pub struct FetchLatestDocumentsResponse {
pub latest_documents: Vec<DocumentVersionWithoutContent>,
@ -28,8 +30,9 @@ pub struct FetchLatestDocumentsResponse {
}
/// Response to an update document request.
#[derive(Debug, Clone, Serialize, JsonSchema)]
#[derive(TS, Debug, Clone, Serialize)]
#[serde(tag = "type")]
#[ts(export)]
pub enum DocumentUpdateResponse {
/// Returned when the created/updated document's content is the same as was
/// sent in the create/update request and thus the response doesn't contain

View file

@ -1,33 +1,29 @@
use aide_axum_typed_multipart::TypedMultipart;
use anyhow::{Context as _, anyhow};
use axum::{
Extension,
Extension, Json,
extract::{Path, State},
};
use axum_extra::TypedHeader;
use axum_jsonschema::Json;
use axum_typed_multipart::TypedMultipart;
use log::info;
use schemars::JsonSchema;
use serde::Deserialize;
use sync_lib::{base64_to_bytes, is_file_type_mergable, merge};
use sync_lib::{is_file_type_mergable, merge};
use super::{
device_id_header::DeviceIdHeader,
requests::{UpdateDocumentVersion, UpdateDocumentVersionMultipart},
device_id_header::DeviceIdHeader, requests::UpdateDocumentVersion,
responses::DocumentUpdateResponse,
};
use crate::{
app_state::{
AppState,
database::models::{DocumentId, StoredDocumentVersion, VaultId, VaultUpdateId},
database::models::{DocumentId, StoredDocumentVersion, VaultId},
},
config::user_config::User,
errors::{SyncServerError, client_error, not_found_error, server_error},
errors::{SyncServerError, not_found_error, server_error},
utils::{dedup_paths::dedup_paths, normalize::normalize, sanitize_path::sanitize_path},
};
// This is required for aide to infer the path parameter types and names
#[derive(Deserialize, JsonSchema)]
#[derive(Deserialize)]
pub struct UpdateDocumentPathParams {
#[serde(deserialize_with = "normalize")]
vault_id: VaultId,
@ -36,7 +32,8 @@ pub struct UpdateDocumentPathParams {
}
#[axum::debug_handler]
pub async fn update_document_multipart(
#[allow(clippy::too_many_lines)]
pub async fn update_document(
Path(UpdateDocumentPathParams {
vault_id,
document_id,
@ -44,79 +41,25 @@ pub async fn update_document_multipart(
Extension(user): Extension<User>,
TypedHeader(device_id): TypedHeader<DeviceIdHeader>,
State(state): State<AppState>,
TypedMultipart(axum_typed_multipart::TypedMultipart(request)): TypedMultipart<
UpdateDocumentVersionMultipart,
>,
) -> Result<Json<DocumentUpdateResponse>, SyncServerError> {
internal_update_document(
user,
device_id,
state,
vault_id,
document_id,
request.parent_version_id,
request.relative_path,
request.content.contents.to_vec(),
)
.await
}
#[axum::debug_handler]
pub async fn update_document_json(
Path(UpdateDocumentPathParams {
vault_id,
document_id,
}): Path<UpdateDocumentPathParams>,
Extension(user): Extension<User>,
TypedHeader(device_id): TypedHeader<DeviceIdHeader>,
State(state): State<AppState>,
Json(request): Json<UpdateDocumentVersion>,
) -> Result<Json<DocumentUpdateResponse>, SyncServerError> {
let content_bytes = base64_to_bytes(&request.content_base64)
.context("Failed to decode base64 content in request")
.map_err(client_error)?;
internal_update_document(
user,
device_id,
state,
vault_id,
document_id,
request.parent_version_id,
request.relative_path,
content_bytes,
)
.await
}
#[allow(clippy::too_many_arguments, clippy::too_many_lines)]
async fn internal_update_document(
user: User,
device_id: DeviceIdHeader,
state: AppState,
vault_id: VaultId,
document_id: DocumentId,
parent_version_id: VaultUpdateId,
relative_path: String,
content: Vec<u8>,
TypedMultipart(request): TypedMultipart<UpdateDocumentVersion>,
) -> Result<Json<DocumentUpdateResponse>, SyncServerError> {
// No need for a transaction as document versions are immutable
let parent_document = state
.database
.get_document_version(&vault_id, parent_version_id, None)
.get_document_version(&vault_id, request.parent_version_id, None)
.await
.map_err(server_error)?
.map_or_else(
|| {
Err(not_found_error(anyhow!(
"Parent version with id `{}` not found",
parent_version_id
request.parent_version_id
)))
},
Ok,
)?;
let sanitized_relative_path = sanitize_path(&relative_path);
let sanitized_relative_path = sanitize_path(&request.relative_path);
let mut transaction = state
.database
@ -156,6 +99,8 @@ async fn internal_update_document(
)));
}
let content = request.content.contents.to_vec();
// Return the latest version if the content and path are the same as the latest
// version
if content == latest_version.content && sanitized_relative_path == latest_version.relative_path

View file

@ -3,13 +3,9 @@
set -e
rm -rf backend/sync_server/bindings
cd backend
cargo test export_bindings
cd -
cp -r backend/sync_server/bindings/* frontend/sync-client/src/services/types/
./scripts/utils/wait-for-server.sh
npm install -g openapi-typescript
openapi-typescript http://localhost:3000/api.json --output frontend/sync-client/src/services/types/http-api.ts