+89
-36
upload/src/main.rs
+89
-36
upload/src/main.rs
···
1
use clap::{ArgAction, Parser};
2
-
use jacquard::api::com_atproto::repo::apply_writes::{self, ApplyWrites, ApplyWritesWritesItem};
3
-
use jacquard::atproto;
4
use jacquard::client::MemorySessionStore;
5
-
use jacquard::prelude::XrpcClient;
6
use jacquard::types::string::{AtStrError, RecordKey, Rkey};
7
use jacquard::{
8
Data,
9
api::com_atproto::{self, repo::list_records::ListRecords},
···
13
types::{ident::AtIdentifier, nsid::Nsid, string::AtprotoStr, uri::Uri},
14
xrpc::XrpcExt,
15
};
16
-
use miette::{ErrReport, IntoDiagnostic, Result};
17
use std::{collections::HashMap, fs, path::PathBuf};
18
19
use crate::sitemap::{BlobRef, Sitemap, SitemapNode};
···
21
mod sitemap;
22
mod utils;
23
24
-
#[derive(Parser, Debug)]
25
#[command(version, about, long_about = None)]
26
struct Config {
27
/// Handle or DID to authenticate
28
-
#[arg(verbatim_doc_comment, short, long)]
29
user: String,
30
/// App password to authenticate the client
31
/// Normal passwords also work but are not advised
32
-
#[arg(verbatim_doc_comment, short, long)]
33
-
password: String,
34
35
/// Include dotfiles in upload
36
/// Default: false
···
48
dir: PathBuf,
49
}
50
51
-
#[tokio::main]
52
-
async fn main() -> Result<(), miette::Error> {
53
-
env_logger::init();
54
-
// get config items
55
-
let config = Config::parse();
56
-
57
-
// get local site info
58
-
let local_sitemap = sitemap::local_sitemap(config.dir, config.all_files, config.git_ignore)?;
59
-
60
-
// create session
61
-
let client = JacquardResolver::default();
62
-
let store = MemorySessionStore::default();
63
-
let session = CredentialSession::new(store.into(), client.into());
64
-
65
-
let auth = session
66
-
.login(config.user.into(), config.password.into(), None, None, None)
67
-
.await?;
68
-
println!("Authenticated as {}", auth.did);
69
-
70
-
71
-
let agent = Agent::from(session);
72
-
73
// find live site records
74
let mut cursor = None;
75
let mut remote_records = Vec::new();
76
loop {
77
let req = com_atproto::repo::list_records::ListRecords::new()
78
.collection(
79
Nsid::new("dev.atcities.route").expect("failed to generate dev.atcities.route nsid"),
80
)
81
-
.repo(AtIdentifier::Did(auth.did.clone()))
82
.limit(100)
83
.maybe_cursor(cursor)
84
.build();
···
120
break;
121
}
122
}
123
124
// upload local site blobs
125
let mut new_sitemap: Sitemap = HashMap::new();
126
for (k, v) in local_sitemap {
···
131
}
132
};
133
let blob = fs::read(blob).into_diagnostic()?;
134
-
// let res = agent
135
-
// .upload_blob(blob, v.mime_type.clone().into())
136
-
// .await?;
137
138
let req = com_atproto::repo::upload_blob::UploadBlob::new()
139
.body(blob.into())
···
149
);
150
}
151
152
// batch delete/upload records
153
let mut writes = Vec::new();
154
let mut delete_records = remote_records
···
198
writes.append(&mut create_records);
199
200
let req = com_atproto::repo::apply_writes::ApplyWrites::new()
201
-
.repo(AtIdentifier::Did(auth.did.clone()))
202
.writes(writes)
203
.build();
204
···
208
.await?
209
.into_output()?;
210
211
-
println!("res: {res:#?}");
212
213
Ok(())
214
}
···
1
use clap::{ArgAction, Parser};
2
+
use jacquard::api::com_atproto::repo::apply_writes::{
3
+
self, ApplyWrites, ApplyWritesOutput, ApplyWritesWritesItem,
4
+
};
5
use jacquard::client::MemorySessionStore;
6
+
use jacquard::client::{AgentSessionExt};
7
+
use jacquard::oauth::loopback::LoopbackConfig;
8
+
use jacquard::oauth::types::AuthorizeOptions;
9
use jacquard::types::string::{AtStrError, RecordKey, Rkey};
10
+
use jacquard::{atproto, oauth};
11
use jacquard::{
12
Data,
13
api::com_atproto::{self, repo::list_records::ListRecords},
···
17
types::{ident::AtIdentifier, nsid::Nsid, string::AtprotoStr, uri::Uri},
18
xrpc::XrpcExt,
19
};
20
+
use miette::{IntoDiagnostic, Result};
21
use std::{collections::HashMap, fs, path::PathBuf};
22
23
use crate::sitemap::{BlobRef, Sitemap, SitemapNode};
···
25
mod sitemap;
26
mod utils;
27
28
+
#[derive(Parser, Debug, Clone)]
29
#[command(version, about, long_about = None)]
30
struct Config {
31
/// Handle or DID to authenticate
32
+
#[arg(verbatim_doc_comment)]
33
user: String,
34
+
35
/// App password to authenticate the client
36
/// Normal passwords also work but are not advised
37
+
/// If ommited, oauth will be used instead
38
+
/// Oauth is reccomended where possible.
39
+
#[arg(verbatim_doc_comment, short = 'p', long = "password")]
40
+
password: Option<String>,
41
42
/// Include dotfiles in upload
43
/// Default: false
···
55
dir: PathBuf,
56
}
57
58
+
async fn live_records(agent: &impl AgentSessionExt, config: Config) -> Result<Vec<String>> {
59
// find live site records
60
let mut cursor = None;
61
let mut remote_records = Vec::new();
62
+
let user = config.user.clone();
63
+
let user = if user.contains(":") {
64
+
AtIdentifier::Did(user.into())
65
+
} else {
66
+
AtIdentifier::Handle(user.into())
67
+
};
68
loop {
69
let req = com_atproto::repo::list_records::ListRecords::new()
70
.collection(
71
Nsid::new("dev.atcities.route").expect("failed to generate dev.atcities.route nsid"),
72
)
73
+
.repo(user.clone())
74
.limit(100)
75
.maybe_cursor(cursor)
76
.build();
···
112
break;
113
}
114
}
115
+
Ok(remote_records)
116
+
}
117
118
+
async fn upload_site_blobs(
119
+
agent: &impl AgentSessionExt,
120
+
_config: Config,
121
+
local_sitemap: Sitemap,
122
+
) -> Result<Sitemap> {
123
// upload local site blobs
124
let mut new_sitemap: Sitemap = HashMap::new();
125
for (k, v) in local_sitemap {
···
130
}
131
};
132
let blob = fs::read(blob).into_diagnostic()?;
133
134
let req = com_atproto::repo::upload_blob::UploadBlob::new()
135
.body(blob.into())
···
145
);
146
}
147
148
+
Ok(new_sitemap)
149
+
}
150
+
151
+
async fn update_remote_site(
152
+
agent: &impl AgentSessionExt,
153
+
config: Config,
154
+
remote_records: Vec<String>,
155
+
new_sitemap: Sitemap,
156
+
) -> Result<ApplyWritesOutput<'static>> {
157
// batch delete/upload records
158
let mut writes = Vec::new();
159
let mut delete_records = remote_records
···
203
writes.append(&mut create_records);
204
205
let req = com_atproto::repo::apply_writes::ApplyWrites::new()
206
+
.repo(AtIdentifier::Did(config.user.into()))
207
.writes(writes)
208
.build();
209
···
213
.await?
214
.into_output()?;
215
216
+
Ok(res)
217
+
}
218
+
219
+
#[tokio::main]
220
+
async fn main() -> Result<(), miette::Error> {
221
+
env_logger::init();
222
+
// get config items
223
+
let config = Config::parse();
224
+
225
+
// get local site info
226
+
let local_sitemap =
227
+
sitemap::local_sitemap(config.dir.clone(), config.all_files, config.git_ignore)?;
228
+
229
+
// create session
230
+
if let Some(password) = config.password.clone() {
231
+
let password = password.into();
232
+
let client = JacquardResolver::default();
233
+
let store = MemorySessionStore::default();
234
+
let session = CredentialSession::new(store.into(), client.into());
235
+
236
+
let _ = session
237
+
.login(config.user.clone().into(), password, None, None, None)
238
+
.await?;
239
+
240
+
let agent = Agent::from(session);
241
+
242
+
let remote_sitemap = live_records(&agent, config.clone()).await?;
243
+
let new_sitemap = upload_site_blobs(&agent, config.clone(), local_sitemap).await?;
244
+
let writes_output =
245
+
update_remote_site(&agent, config.clone(), remote_sitemap, new_sitemap).await?;
246
+
println!("{writes_output:#?}");
247
+
} else {
248
+
let oauth = oauth::client::OAuthClient::with_memory_store();
249
+
let session = oauth
250
+
.login_with_local_server(
251
+
config.user.clone(),
252
+
AuthorizeOptions::default(),
253
+
LoopbackConfig::default(),
254
+
)
255
+
.await?;
256
+
257
+
let agent = Agent::from(session);
258
+
259
+
let remote_sitemap = live_records(&agent, config.clone()).await?;
260
+
let new_sitemap = upload_site_blobs(&agent, config.clone(), local_sitemap).await?;
261
+
let writes_output =
262
+
update_remote_site(&agent, config.clone(), remote_sitemap, new_sitemap).await?;
263
+
println!("{writes_output:#?}");
264
+
};
265
266
Ok(())
267
}