[WIP] A (somewhat barebones) atproto app for creating custom sites without hosting!

upload: authenticate user when uploading blobs && include nice final message && misc bug fixes

vielle.dev fd78cc4d af7b6f20

verified
Changed files
+57 -11
upload
+47 -11
upload/src/main.rs
··· 7 7 use jacquard::oauth::loopback::LoopbackConfig; 8 8 use jacquard::oauth::types::AuthorizeOptions; 9 9 use jacquard::types::string::{AtStrError, RecordKey, Rkey}; 10 + use jacquard::{AuthorizationToken, CowStr, atproto, oauth}; 10 11 use jacquard::{ 11 12 Data, 12 13 api::com_atproto::{self, repo::list_records::ListRecords}, ··· 16 17 types::{ident::AtIdentifier, nsid::Nsid, string::AtprotoStr, uri::Uri}, 17 18 xrpc::XrpcExt, 18 19 }; 19 - use jacquard::{atproto, oauth}; 20 20 use miette::{Context, IntoDiagnostic, Result}; 21 21 use std::{collections::HashMap, fs, path::PathBuf}; 22 22 ··· 140 140 k, 141 141 SitemapNode { 142 142 mime_type: v.mime_type, 143 - blob: BlobRef::Remote(res.blob), 143 + blob: BlobRef::Remote(res.blob.into()), 144 144 }, 145 145 ); 146 146 } ··· 151 151 async fn update_remote_site( 152 152 agent: &impl AgentSessionExt, 153 153 config: Config, 154 + auth: AuthorizationToken<'_>, 154 155 remote_records: Vec<String>, 155 156 new_sitemap: Sitemap, 156 157 ) -> Result<ApplyWritesOutput<'static>> { ··· 185 186 "$type": "dev.atcities.route#blob", 186 187 "blob": { 187 188 "$type": "blob", 188 - "ref": blob.r#ref.as_str(), 189 + "ref": { 190 + "$link": blob.r#ref.as_str() 191 + }, 189 192 "mimeType": blob.mime_type.0.as_str(), 190 193 "size": blob.size 191 194 } ··· 203 206 204 207 writes.append(&mut delete_records); 205 208 writes.append(&mut create_records); 209 + 210 + let repo = if config.user.contains(":") { 211 + AtIdentifier::Did(config.user.into()) 212 + } else { 213 + AtIdentifier::Handle(config.user.into()) 214 + }; 206 215 207 216 let req = com_atproto::repo::apply_writes::ApplyWrites::new() 208 - .repo(AtIdentifier::Did(config.user.into())) 217 + .repo(repo) 209 218 .writes(writes) 210 219 .build(); 211 220 212 221 let res = agent 213 222 .xrpc(agent.endpoint().await) 223 + .auth(auth) 214 224 .send::<ApplyWrites>(&req) 215 225 .await? 216 226 .into_output()?; ··· 235 245 let store = MemorySessionStore::default(); 236 246 let session = CredentialSession::new(store.into(), client.into()); 237 247 238 - let _ = session 248 + let auth = session 239 249 .login(config.user.clone().into(), password, None, None, None) 240 250 .await?; 241 251 ··· 243 253 244 254 let remote_sitemap = live_records(&agent, config.clone()).await?; 245 255 let new_sitemap = upload_site_blobs(&agent, config.clone(), local_sitemap).await?; 246 - let writes_output = 247 - update_remote_site(&agent, config.clone(), remote_sitemap, new_sitemap).await?; 248 - println!("{writes_output:#?}"); 256 + let _ = update_remote_site( 257 + &agent, 258 + config.clone(), 259 + AuthorizationToken::Bearer(auth.access_jwt), 260 + remote_sitemap, 261 + new_sitemap, 262 + ) 263 + .await?; 264 + 265 + println!("Site is now updated. Live at {}", utils::site_handle(config.user)); 249 266 } else { 250 267 let oauth = oauth::client::OAuthClient::with_memory_store(); 251 268 let session = oauth ··· 256 273 ) 257 274 .await?; 258 275 276 + // sick and twisted reference mangling BUT it works So 277 + // tldr: the cowstr is a borrowed cowstr iiuc, 278 + // so it needs to be turned into an owned cowstr 279 + // to break reference to session which gets moved 280 + let auth = session.access_token().await; 281 + let auth = match auth { 282 + AuthorizationToken::Bearer(cow_str) => CowStr::copy_from_str(cow_str.as_str()), 283 + AuthorizationToken::Dpop(cow_str) => CowStr::copy_from_str(cow_str.as_str()), 284 + }; 285 + 286 + println!("{}", auth); 287 + 259 288 let agent = Agent::from(session); 260 289 261 290 let remote_sitemap = live_records(&agent, config.clone()).await?; 262 291 let new_sitemap = upload_site_blobs(&agent, config.clone(), local_sitemap).await?; 263 - let writes_output = 264 - update_remote_site(&agent, config.clone(), remote_sitemap, new_sitemap).await?; 265 - println!("{writes_output:#?}"); 292 + let _ = update_remote_site( 293 + &agent, 294 + config.clone(), 295 + AuthorizationToken::Dpop(auth), 296 + remote_sitemap, 297 + new_sitemap, 298 + ) 299 + .await?; 300 + 301 + println!("Site is now updated. Live at {}", utils::site_handle(config.user)); 266 302 }; 267 303 268 304 Ok(())
+10
upload/src/utils.rs
··· 1 1 use regex::Regex; 2 2 3 + #[allow(dead_code)] 3 4 pub fn rkey_to_url(rkey: String) -> Option<String> { 4 5 let regex = Regex::new( 5 6 // symbols A-Za-z0-9 -._~: are all valid rkey characters ··· 62 63 63 64 Some(res) 64 65 } 66 + 67 + pub fn site_handle(user: String) -> String { 68 + if user.contains(":") { 69 + let user = user.split(":").collect::<Vec<_>>(); 70 + let method = user[1]; 71 + let did = user[2]; 72 + format!("https://{did}.did-{method}.atcities.dev/") 73 + } else { format!("https://{user}.atcities.dev/") } 74 + }