bctx_cloud_core/client/
sync.rs1#[cfg(not(feature = "cloud-server"))]
2use anyhow::bail;
3use anyhow::Result;
4use serde::{Deserialize, Serialize};
5use vault::fact::MemoFact;
6
7#[derive(Debug, Serialize, Deserialize)]
8pub struct SyncPushPayload {
9 pub project_hash: String,
10 pub facts: Vec<MemoFact>,
11 pub client_version: String,
12}
13
14#[derive(Debug, Serialize, Deserialize)]
15pub struct SyncPullResponse {
16 pub facts: Vec<MemoFact>,
17 pub server_version: u64,
18}
19
20#[derive(Debug, Serialize, Deserialize)]
21pub struct SyncPushResponse {
22 pub accepted: usize,
23 pub merged: usize,
24 pub server_version: u64,
25}
26
27#[cfg(feature = "cloud-server")]
29pub async fn push_vault(
30 endpoint: &str,
31 token: &str,
32 project_hash: &str,
33 facts: Vec<MemoFact>,
34) -> Result<SyncPushResponse> {
35 let payload = SyncPushPayload {
36 project_hash: project_hash.to_string(),
37 facts,
38 client_version: env!("CARGO_PKG_VERSION").to_string(),
39 };
40 let resp = reqwest::Client::builder()
41 .timeout(std::time::Duration::from_secs(15))
42 .connect_timeout(std::time::Duration::from_secs(8))
43 .build()?
44 .post(format!("{endpoint}/sync/vault"))
45 .bearer_auth(token)
46 .json(&payload)
47 .send()
48 .await?
49 .error_for_status()?
50 .json::<SyncPushResponse>()
51 .await?;
52 Ok(resp)
53}
54
55#[cfg(not(feature = "cloud-server"))]
56pub async fn push_vault(
57 _endpoint: &str,
58 _token: &str,
59 _project_hash: &str,
60 _facts: Vec<MemoFact>,
61) -> Result<SyncPushResponse> {
62 bail!("cloud-server feature not enabled")
63}
64
65#[cfg(feature = "cloud-server")]
67pub async fn pull_vault(
68 endpoint: &str,
69 token: &str,
70 project_hash: &str,
71) -> Result<SyncPullResponse> {
72 let resp = reqwest::Client::builder()
73 .timeout(std::time::Duration::from_secs(15))
74 .connect_timeout(std::time::Duration::from_secs(8))
75 .build()?
76 .get(format!("{endpoint}/sync/vault/{project_hash}"))
77 .bearer_auth(token)
78 .send()
79 .await?
80 .error_for_status()?
81 .json::<SyncPullResponse>()
82 .await?;
83 Ok(resp)
84}
85
86#[cfg(not(feature = "cloud-server"))]
87pub async fn pull_vault(
88 _endpoint: &str,
89 _token: &str,
90 _project_hash: &str,
91) -> Result<SyncPullResponse> {
92 bail!("cloud-server feature not enabled")
93}
94
95#[cfg(feature = "cloud-server")]
98pub fn push_usage_snapshot(
99 endpoint: &str,
100 token: &str,
101 snapshot: &crate::usage::UsageSnapshot,
102) -> anyhow::Result<()> {
103 let rt = tokio::runtime::Builder::new_current_thread()
104 .enable_all()
105 .build()?;
106 rt.block_on(async {
107 let client = reqwest::Client::builder()
108 .timeout(std::time::Duration::from_secs(10))
109 .connect_timeout(std::time::Duration::from_secs(5))
110 .build()?;
111 let resp: serde_json::Value = client
112 .post(format!("{endpoint}/sync/usage"))
113 .bearer_auth(token)
114 .json(snapshot)
115 .send()
116 .await?
117 .error_for_status()?
118 .json()
119 .await?;
120 if resp["ok"] == serde_json::json!(true) {
121 Ok(())
122 } else {
123 anyhow::bail!("snapshot rejected: {}", resp["reason"])
124 }
125 })
126}
127
128#[cfg(not(feature = "cloud-server"))]
129pub fn push_usage_snapshot(
130 _endpoint: &str,
131 _token: &str,
132 _snapshot: &crate::usage::UsageSnapshot,
133) -> anyhow::Result<()> {
134 bail!("cloud-server feature not enabled")
135}
136
137#[cfg(all(test, not(feature = "cloud-server")))]
138mod stub_tests {
139 #[test]
140 fn without_the_cloud_client_a_snapshot_is_never_reported_as_sent() {
141 let snapshot = crate::usage::UsageSnapshot {
142 device_id: "11111111-1111-4111-8111-111111111111".into(),
143 client_version: "test".into(),
144 rows: vec![],
145 };
146 assert!(super::push_usage_snapshot("http://127.0.0.1:9", "t", &snapshot).is_err());
147 }
148}