//! The push/pull changelog wire types, shared by personal sync and group //! sync and by nothing else. //! //! Six types is the whole shared surface between [`super::sync`] and //! [`super::groups`]; a seventh belongs in whichever of them uses it. use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use crate::db::{SyncDeviceId, SyncOperation}; #[derive(Deserialize, utoipa::ToSchema)] pub(crate) struct PushRequest { #[schema(value_type = String)] pub device_id: SyncDeviceId, /// Client-generated UUID for idempotent push. If a push with the same /// batch_id has already been committed, the server returns the existing /// cursor without re-inserting. pub batch_id: uuid::Uuid, pub changes: Vec, } #[derive(Deserialize, utoipa::ToSchema)] pub(crate) struct ChangeEntry { pub table: String, #[schema(value_type = String)] pub op: SyncOperation, pub row_id: String, #[schema(value_type = String)] pub timestamp: DateTime, pub data: Option, } #[derive(Serialize, utoipa::ToSchema)] pub(crate) struct PushResponse { pub(super) cursor: i64, } #[derive(Deserialize, utoipa::ToSchema)] pub(crate) struct PullRequest { #[schema(value_type = String)] pub device_id: SyncDeviceId, pub cursor: i64, /// Optional table name filter; only return entries for these tables. #[serde(default)] pub tables: Option>, /// Optional timestamp filter; only return entries at or after this time. #[serde(default)] #[schema(value_type = Option)] pub since: Option>, } #[derive(Serialize, utoipa::ToSchema)] pub(crate) struct PullResponse { pub(super) changes: Vec, pub(super) cursor: i64, pub(super) has_more: bool, } #[derive(Serialize, utoipa::ToSchema)] pub(crate) struct PullChangeEntry { pub(super) seq: i64, #[schema(value_type = String)] pub(super) device_id: SyncDeviceId, pub(super) table: String, pub(super) op: String, pub(super) row_id: String, #[schema(value_type = String)] pub(super) timestamp: DateTime, pub(super) data: Option, /// Which encryption key was used. Null means key_id 1 (pre-rotation). #[serde(skip_serializing_if = "Option::is_none")] pub(super) key_id: Option, /// For a group entry, the GCK generation its ciphertext is sealed under. The /// member resolves that generation's grant to decrypt it, which is how entries /// written before a rotation stay readable. Absent on personal entries, which /// key off `key_id` instead. #[serde(skip_serializing_if = "Option::is_none")] pub(super) gck_version: Option, }