Alternative ATProto PDS implementation

cargo fix

+8 -8
src/auth.rs
··· 8 8 use axum::{extract::FromRequestParts, http::StatusCode}; 9 9 use base64::Engine; 10 10 11 - use crate::{AppState, Error, auth, error::ErrorMessage}; 11 + use crate::{AppState, Error, error::ErrorMessage}; 12 12 13 13 /// This is an axum request extractor that represents an authenticated user. 14 14 /// 15 15 /// If specified in an API endpoint, this will guarantee that the API can only be called 16 16 /// by an authenticated user. 17 - pub struct AuthenticatedUser { 17 + pub(crate) struct AuthenticatedUser { 18 18 did: String, 19 19 } 20 20 21 21 impl AuthenticatedUser { 22 - pub fn did(&self) -> String { 22 + pub(crate) fn did(&self) -> String { 23 23 self.did.clone() 24 24 } 25 25 } 26 26 27 27 impl FromRequestParts<AppState> for AuthenticatedUser { 28 - type Rejection = crate::Error; 28 + type Rejection = Error; 29 29 30 30 async fn from_request_parts( 31 31 parts: &mut axum::http::request::Parts, 32 32 state: &AppState, 33 - ) -> std::result::Result<Self, Self::Rejection> { 33 + ) -> Result<Self, Self::Rejection> { 34 34 let token = parts 35 35 .headers 36 36 .get(axum::http::header::AUTHORIZATION) ··· 52 52 53 53 // N.B: We ignore all fields inside of the token up until this point because they can be 54 54 // attacker-controlled. 55 - let (typ, claims) = auth::verify(&state.signing_key.did(), token).map_err(|e| { 55 + let (typ, claims) = verify(&state.signing_key.did(), token).map_err(|e| { 56 56 Error::with_status( 57 57 StatusCode::UNAUTHORIZED, 58 58 e.context("failed to verify auth token"), ··· 97 97 } 98 98 99 99 /// Cryptographically sign a JSON web token with the specified key. 100 - pub fn sign( 100 + pub(crate) fn sign( 101 101 key: &Secp256k1Keypair, 102 102 typ: &str, 103 103 claims: serde_json::Value, ··· 120 120 } 121 121 122 122 /// Cryptographically verify a JSON web token's validity using the specified public key. 123 - pub fn verify(key: &str, token: &str) -> anyhow::Result<(String, serde_json::Value)> { 123 + pub(crate) fn verify(key: &str, token: &str) -> anyhow::Result<(String, serde_json::Value)> { 124 124 let mut parts = token.splitn(3, '.'); 125 125 let hdr = parts.next().context("no header")?; 126 126 let claims = parts.next().context("no claims")?;
+8 -8
src/config.rs
··· 3 3 use serde::Deserialize; 4 4 use url::Url; 5 5 6 - pub mod metrics { 6 + pub(crate) mod metrics { 7 7 use super::*; 8 8 9 9 #[derive(Deserialize, Debug, Clone)] 10 - pub struct PrometheusConfig { 10 + pub(crate) struct PrometheusConfig { 11 11 /// The URL of the Prometheus server's exporter endpoint. 12 12 pub url: Url, 13 13 } ··· 15 15 16 16 #[derive(Deserialize, Debug, Clone)] 17 17 #[serde(tag = "type")] 18 - pub enum MetricConfig { 18 + pub(crate) enum MetricConfig { 19 19 PrometheusPush(metrics::PrometheusConfig), 20 20 } 21 21 22 22 #[derive(Deserialize, Debug, Clone)] 23 - pub struct FirehoseConfig { 23 + pub(crate) struct FirehoseConfig { 24 24 /// A list of upstream relays that this PDS will try to reach out to. 25 25 pub relays: Vec<Url>, 26 26 } 27 27 28 28 #[derive(Deserialize, Debug, Clone)] 29 - pub struct RepoConfig { 29 + pub(crate) struct RepoConfig { 30 30 /// The path to the repository storage. 31 31 pub path: PathBuf, 32 32 } 33 33 34 34 #[derive(Deserialize, Debug, Clone)] 35 - pub struct PlcConfig { 35 + pub(crate) struct PlcConfig { 36 36 /// The path to the local PLC cache. 37 37 pub path: PathBuf, 38 38 } 39 39 40 40 #[derive(Deserialize, Debug, Clone)] 41 - pub struct BlobConfig { 41 + pub(crate) struct BlobConfig { 42 42 /// The path to store blobs into. 43 43 pub path: PathBuf, 44 44 /// The maximum size limit of blobs. ··· 46 46 } 47 47 48 48 #[derive(Deserialize, Debug, Clone)] 49 - pub struct AppConfig { 49 + pub(crate) struct AppConfig { 50 50 /// The primary signing keys for all PLC/DID operations. 51 51 pub key: PathBuf, 52 52 /// The hostname of the PDS. Typically a domain name.
+4 -4
src/did.rs
··· 10 10 11 11 #[derive(Clone, Debug, Serialize, Deserialize)] 12 12 #[serde(rename_all = "camelCase")] 13 - pub struct DidVerificationMethod { 13 + pub(crate) struct DidVerificationMethod { 14 14 pub id: String, 15 15 #[serde(rename = "type")] 16 16 pub ty: String, ··· 20 20 21 21 #[derive(Clone, Debug, Serialize, Deserialize)] 22 22 #[serde(rename_all = "camelCase")] 23 - pub struct DidService { 23 + pub(crate) struct DidService { 24 24 pub id: String, 25 25 #[serde(rename = "type")] 26 26 pub ty: String, ··· 29 29 30 30 #[derive(Clone, Debug, Serialize, Deserialize)] 31 31 #[serde(rename_all = "camelCase")] 32 - pub struct DidDocument { 32 + pub(crate) struct DidDocument { 33 33 #[serde(rename = "@context", skip_serializing_if = "Vec::is_empty")] 34 34 pub context: Vec<Url>, 35 35 pub id: Did, ··· 39 39 } 40 40 41 41 /// Resolve a DID document using the specified reqwest client. 42 - pub async fn resolve(client: &Client, did: Did) -> Result<DidDocument> { 42 + pub(crate) async fn resolve(client: &Client, did: Did) -> Result<DidDocument> { 43 43 let url = match did.method() { 44 44 "did:web" => { 45 45 // N.B: This is a potentially hostile operation, so we are only going to allow
+1 -1
src/endpoints/identity.rs
··· 181 181 } 182 182 183 183 #[rustfmt::skip] 184 - pub fn routes() -> Router<AppState> { 184 + pub(super) fn routes() -> Router<AppState> { 185 185 // AP /xrpc/com.atproto.identity.updateHandle 186 186 // AP /xrpc/com.atproto.identity.requestPlcOperationSignature 187 187 // AP /xrpc/com.atproto.identity.signPlcOperation
+2 -2
src/endpoints/mod.rs
··· 8 8 mod server; 9 9 mod sync; 10 10 11 - pub async fn health() -> Result<Json<serde_json::Value>> { 11 + pub(crate) async fn health() -> Result<Json<serde_json::Value>> { 12 12 Ok(Json(json!({ 13 13 "version": "bluepds" 14 14 }))) 15 15 } 16 16 17 - pub fn routes() -> Router<AppState> { 17 + pub(crate) fn routes() -> Router<AppState> { 18 18 Router::new() 19 19 .route("/_health", get(health)) 20 20 .merge(identity::routes()) // com.atproto.identity
+12 -12
src/endpoints/repo.rs
··· 350 350 351 351 // The swap failed. Return the old commit and do not update the repository. 352 352 return Ok(Json( 353 - repo::apply_writes::OutputData { 353 + apply_writes::OutputData { 354 354 results: None, 355 355 commit: Some( 356 356 CommitMetaData { ··· 438 438 .await; 439 439 440 440 Ok(Json( 441 - repo::apply_writes::OutputData { 441 + apply_writes::OutputData { 442 442 results: Some(res), 443 443 commit: Some( 444 444 CommitMetaData { ··· 702 702 703 703 #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] 704 704 #[serde(rename_all = "camelCase")] 705 - pub struct ListRecordsParameters { 705 + pub(super) struct ListRecordsParameters { 706 706 ///The NSID of the record type. 707 - pub collection: atrium_api::types::string::Nsid, 707 + pub collection: Nsid, 708 708 #[serde(skip_serializing_if = "core::option::Option::is_none")] 709 - pub cursor: core::option::Option<String>, 709 + pub cursor: Option<String>, 710 710 ///The number of records to return. 711 711 #[serde(skip_serializing_if = "core::option::Option::is_none")] 712 - pub limit: core::option::Option<String>, 712 + pub limit: Option<String>, 713 713 ///The handle or DID of the repo. 714 - pub repo: atrium_api::types::string::AtIdentifier, 714 + pub repo: AtIdentifier, 715 715 ///Flag to reverse the order of the returned records. 716 716 #[serde(skip_serializing_if = "core::option::Option::is_none")] 717 - pub reverse: core::option::Option<bool>, 717 + pub reverse: Option<bool>, 718 718 ///DEPRECATED: The highest sort-ordered rkey to stop at (exclusive) 719 719 #[serde(skip_serializing_if = "core::option::Option::is_none")] 720 - pub rkey_end: core::option::Option<String>, 720 + pub rkey_end: Option<String>, 721 721 ///DEPRECATED: The lowest sort-ordered rkey to start from (exclusive) 722 722 #[serde(skip_serializing_if = "core::option::Option::is_none")] 723 - pub rkey_start: core::option::Option<String>, 723 + pub rkey_start: Option<String>, 724 724 } 725 725 726 726 async fn list_records( ··· 845 845 drop(file); 846 846 let hash = sha.finalize(); 847 847 848 - let cid = atrium_repo::Cid::new_v1( 848 + let cid = Cid::new_v1( 849 849 IPLD_RAW, 850 850 atrium_repo::Multihash::wrap(IPLD_MH_SHA2_256, hash.as_slice()).unwrap(), 851 851 ); ··· 885 885 } 886 886 887 887 #[rustfmt::skip] 888 - pub fn routes() -> Router<AppState> { 888 + pub(super) fn routes() -> Router<AppState> { 889 889 // AP /xrpc/com.atproto.repo.applyWrites 890 890 // AP /xrpc/com.atproto.repo.createRecord 891 891 // AP /xrpc/com.atproto.repo.putRecord
+5 -5
src/endpoints/server.rs
··· 373 373 // SEC: Call argon2's `verify_password` to simulate password verification and discard the result. 374 374 // We do this to avoid exposing a timing attack where attackers can measure the response time to 375 375 // determine whether or not an account exists. 376 - let _ = argon2::Argon2::default().verify_password( 376 + let _ = Argon2::default().verify_password( 377 377 password.as_bytes(), 378 378 &PasswordHash::new(DUMMY_PASSWORD).unwrap(), 379 379 ); ··· 384 384 )); 385 385 }; 386 386 387 - match argon2::Argon2::default().verify_password( 387 + match Argon2::default().verify_password( 388 388 password.as_bytes(), 389 389 &PasswordHash::new(account.password.as_str()).context("invalid password hash in db")?, 390 390 ) { ··· 517 517 refresh_jwt: refresh_token, 518 518 519 519 active: Some(active), // TODO? 520 - did: atrium_api::types::string::Did::new(did.to_string()).unwrap(), 520 + did: Did::new(did.to_string()).unwrap(), 521 521 did_doc: None, 522 - handle: atrium_api::types::string::Handle::new(user.handle).unwrap(), 522 + handle: Handle::new(user.handle).unwrap(), 523 523 status, 524 524 } 525 525 .into(), ··· 624 624 } 625 625 626 626 #[rustfmt::skip] 627 - pub fn routes() -> Router<AppState> { 627 + pub(super) fn routes() -> Router<AppState> { 628 628 // UG /xrpc/com.atproto.server.describeServer 629 629 // UP /xrpc/com.atproto.server.createAccount 630 630 // UP /xrpc/com.atproto.server.createSession
+12 -12
src/endpoints/sync.rs
··· 194 194 // HACK: `limit` may be passed as a string, so we must treat it as one. 195 195 #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] 196 196 #[serde(rename_all = "camelCase")] 197 - pub struct ListBlobsParameters { 197 + pub(super) struct ListBlobsParameters { 198 198 #[serde(skip_serializing_if = "core::option::Option::is_none")] 199 - pub cursor: core::option::Option<String>, 199 + pub cursor: Option<String>, 200 200 ///The DID of the repo. 201 - pub did: atrium_api::types::string::Did, 201 + pub did: Did, 202 202 #[serde(skip_serializing_if = "core::option::Option::is_none")] 203 - pub limit: core::option::Option<String>, 203 + pub limit: Option<String>, 204 204 ///Optional revision of the repo to list blobs since. 205 205 #[serde(skip_serializing_if = "core::option::Option::is_none")] 206 - pub since: core::option::Option<String>, 206 + pub since: Option<String>, 207 207 } 208 208 209 209 async fn list_blobs( ··· 224 224 let cids = cids 225 225 .into_iter() 226 226 .map(|c| { 227 - atrium_repo::Cid::from_str(&c) 227 + Cid::from_str(&c) 228 228 .map(atrium_api::types::string::Cid::new) 229 229 .map_err(anyhow::Error::new) 230 230 }) ··· 239 239 // HACK: `limit` may be passed as a string, so we must treat it as one. 240 240 #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] 241 241 #[serde(rename_all = "camelCase")] 242 - pub struct ListReposParameters { 242 + pub(super) struct ListReposParameters { 243 243 #[serde(skip_serializing_if = "core::option::Option::is_none")] 244 - pub cursor: core::option::Option<String>, 244 + pub cursor: Option<String>, 245 245 #[serde(skip_serializing_if = "core::option::Option::is_none")] 246 - pub limit: core::option::Option<String>, 246 + pub limit: Option<String>, 247 247 } 248 248 249 249 async fn list_repos( ··· 309 309 // HACK: `cursor` may be passed as a string, so we must treat it as one. 310 310 #[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)] 311 311 #[serde(rename_all = "camelCase")] 312 - pub struct SubscribeReposParametersData { 312 + pub(super) struct SubscribeReposParametersData { 313 313 ///The last known event seq number to backfill from. 314 314 #[serde(skip_serializing_if = "core::option::Option::is_none")] 315 - pub cursor: core::option::Option<String>, 315 + pub cursor: Option<String>, 316 316 } 317 317 318 318 async fn subscribe_repos( ··· 336 336 } 337 337 338 338 #[rustfmt::skip] 339 - pub fn routes() -> axum::Router<AppState> { 339 + pub(super) fn routes() -> Router<AppState> { 340 340 // UG /xrpc/com.atproto.sync.getBlob 341 341 // UG /xrpc/com.atproto.sync.getBlocks 342 342 // UG /xrpc/com.atproto.sync.getLatestCommit
+1 -1
src/error.rs
··· 86 86 } 87 87 88 88 impl IntoResponse for Error { 89 - fn into_response(self) -> axum::response::Response { 89 + fn into_response(self) -> Response { 90 90 error!("{:?}", self.err); 91 91 92 92 // N.B: Forward out the error message to the requester if this is a debug build.
+9 -9
src/firehose.rs
··· 20 20 21 21 enum FirehoseMessage { 22 22 Broadcast(sync::subscribe_repos::Message), 23 - Connect(Box<(axum::extract::ws::WebSocket, Option<i64>)>), 23 + Connect(Box<(WebSocket, Option<i64>)>), 24 24 } 25 25 26 26 enum FrameHeader { ··· 120 120 121 121 /// A firehose producer. This is used to transmit messages to the firehose for broadcast. 122 122 #[derive(Clone, Debug)] 123 - pub struct FirehoseProducer { 123 + pub(crate) struct FirehoseProducer { 124 124 tx: tokio::sync::mpsc::Sender<FirehoseMessage>, 125 125 } 126 126 127 127 impl FirehoseProducer { 128 128 /// Broadcast an `#account` event. 129 - pub async fn account(&self, account: impl Into<sync::subscribe_repos::Account>) { 129 + pub(crate) async fn account(&self, account: impl Into<sync::subscribe_repos::Account>) { 130 130 let _ = self 131 131 .tx 132 132 .send(FirehoseMessage::Broadcast( ··· 136 136 } 137 137 138 138 /// Broadcast an `#identity` event. 139 - pub async fn identity(&self, identity: impl Into<sync::subscribe_repos::Identity>) { 139 + pub(crate) async fn identity(&self, identity: impl Into<sync::subscribe_repos::Identity>) { 140 140 let _ = self 141 141 .tx 142 142 .send(FirehoseMessage::Broadcast( ··· 146 146 } 147 147 148 148 /// Broadcast a `#commit` event. 149 - pub async fn commit(&self, commit: impl Into<sync::subscribe_repos::Commit>) { 149 + pub(crate) async fn commit(&self, commit: impl Into<sync::subscribe_repos::Commit>) { 150 150 let _ = self 151 151 .tx 152 152 .send(FirehoseMessage::Broadcast( ··· 155 155 .await; 156 156 } 157 157 158 - pub async fn client_connection(&self, ws: WebSocket, cursor: Option<i64>) { 158 + pub(crate) async fn client_connection(&self, ws: WebSocket, cursor: Option<i64>) { 159 159 let _ = self 160 160 .tx 161 161 .send(FirehoseMessage::Connect(Box::new((ws, cursor)))) ··· 213 213 seq: u64, 214 214 history: &VecDeque<(u64, &str, sync::subscribe_repos::Message)>, 215 215 cursor: Option<i64>, 216 - ) -> anyhow::Result<WebSocket> { 216 + ) -> Result<WebSocket> { 217 217 if let Some(cursor) = cursor { 218 218 let mut frame = Vec::new(); 219 219 let cursor = cursor as u64; ··· 257 257 Ok(ws) 258 258 } 259 259 260 - pub async fn reconnect_relays(client: &Client, config: &AppConfig) { 260 + pub(crate) async fn reconnect_relays(client: &Client, config: &AppConfig) { 261 261 // Avoid connecting to upstream relays in test mode. 262 262 if config.test { 263 263 return; ··· 308 308 /// This will broadcast all updates in this PDS out to anyone who is listening. 309 309 /// 310 310 /// Reference: https://atproto.com/specs/sync 311 - pub async fn spawn( 311 + pub(crate) async fn spawn( 312 312 client: Client, 313 313 config: AppConfig, 314 314 ) -> (tokio::task::JoinHandle<()>, FirehoseProducer) {
+4 -4
src/main.rs
··· 42 42 mod plc; 43 43 mod storage; 44 44 45 - pub type Result<T> = std::result::Result<T, error::Error>; 45 + pub type Result<T> = std::result::Result<T, Error>; 46 46 pub use error::Error; 47 47 use uuid::Uuid; 48 48 49 49 pub type Client = reqwest_middleware::ClientWithMiddleware; 50 - pub type Db = sqlx::SqlitePool; 50 + pub type Db = SqlitePool; 51 51 pub type Cred = Arc<dyn TokenCredential>; 52 52 53 53 pub const APP_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"),); ··· 191 191 } 192 192 193 193 #[rustfmt::skip] 194 - pub fn routes() -> Router<AppState> { 194 + pub(crate) fn routes() -> Router<AppState> { 195 195 // AP /xrpc/app.bsky.actor.putPreferences 196 196 // AG /xrpc/app.bsky.actor.getPreferences 197 197 Router::new() ··· 289 289 let r = client 290 290 .request(request.method().clone(), url) 291 291 .headers(h) 292 - .header(axum::http::header::AUTHORIZATION, format!("Bearer {token}")) 292 + .header(http::header::AUTHORIZATION, format!("Bearer {token}")) 293 293 .body(reqwest::Body::wrap_stream( 294 294 request.into_body().into_data_stream(), 295 295 ))
+10 -10
src/metrics.rs
··· 8 8 9 9 use crate::config; 10 10 11 - pub const AUTH_FAILED: &str = "bluepds.auth.failed"; // Counter. 11 + pub(crate) const AUTH_FAILED: &str = "bluepds.auth.failed"; // Counter. 12 12 13 - pub const FIREHOSE_HISTORY: &str = "bluepds.firehose.history"; // Gauge. 14 - pub const FIREHOSE_LISTENERS: &str = "bluepds.firehose.listeners"; // Gauge. 15 - pub const FIREHOSE_MESSAGES: &str = "bluepds.firehose.messages"; // Counter. 16 - pub const FIREHOSE_SEQUENCE: &str = "bluepds.firehose.sequence"; // Counter. 13 + pub(crate) const FIREHOSE_HISTORY: &str = "bluepds.firehose.history"; // Gauge. 14 + pub(crate) const FIREHOSE_LISTENERS: &str = "bluepds.firehose.listeners"; // Gauge. 15 + pub(crate) const FIREHOSE_MESSAGES: &str = "bluepds.firehose.messages"; // Counter. 16 + pub(crate) const FIREHOSE_SEQUENCE: &str = "bluepds.firehose.sequence"; // Counter. 17 17 18 - pub const REPO_COMMITS: &str = "bluepds.repo.commits"; // Counter. 19 - pub const REPO_OP_CREATE: &str = "bluepds.repo.op.create"; // Counter. 20 - pub const REPO_OP_UPDATE: &str = "bluepds.repo.op.update"; // Counter. 21 - pub const REPO_OP_DELETE: &str = "bluepds.repo.op.delete"; // Counter. 18 + pub(crate) const REPO_COMMITS: &str = "bluepds.repo.commits"; // Counter. 19 + pub(crate) const REPO_OP_CREATE: &str = "bluepds.repo.op.create"; // Counter. 20 + pub(crate) const REPO_OP_UPDATE: &str = "bluepds.repo.op.update"; // Counter. 21 + pub(crate) const REPO_OP_DELETE: &str = "bluepds.repo.op.delete"; // Counter. 22 22 23 23 /// Must be ran exactly once on startup. This will declare all of the instruments for `metrics`. 24 - pub fn setup(config: &Option<config::MetricConfig>) -> anyhow::Result<()> { 24 + pub(crate) fn setup(config: &Option<config::MetricConfig>) -> anyhow::Result<()> { 25 25 describe_counter!(AUTH_FAILED, "The number of failed authentication attempts."); 26 26 27 27 describe_gauge!(FIREHOSE_HISTORY, "The size of the firehose history buffer.");
+6 -6
src/plc.rs
··· 12 12 13 13 #[derive(Debug, Deserialize, Serialize, Clone)] 14 14 #[serde(rename_all = "camelCase", tag = "type")] 15 - pub enum PlcService { 15 + pub(crate) enum PlcService { 16 16 #[serde(rename = "AtprotoPersonalDataServer")] 17 17 Pds { endpoint: String }, 18 18 } 19 19 20 20 #[derive(Debug, Deserialize, Serialize, Clone)] 21 21 #[serde(rename_all = "camelCase")] 22 - pub struct PlcOperation { 22 + pub(crate) struct PlcOperation { 23 23 #[serde(rename = "type")] 24 24 pub typ: String, 25 25 pub rotation_keys: Vec<String>, ··· 30 30 } 31 31 32 32 impl PlcOperation { 33 - pub fn sign(self, sig: Vec<u8>) -> SignedPlcOperation { 33 + pub(crate) fn sign(self, sig: Vec<u8>) -> SignedPlcOperation { 34 34 SignedPlcOperation { 35 35 typ: self.typ, 36 36 rotation_keys: self.rotation_keys, ··· 45 45 46 46 #[derive(Debug, Deserialize, Serialize, Clone)] 47 47 #[serde(rename_all = "camelCase")] 48 - pub struct SignedPlcOperation { 48 + pub(crate) struct SignedPlcOperation { 49 49 #[serde(rename = "type")] 50 50 pub typ: String, 51 51 pub rotation_keys: Vec<String>, ··· 56 56 pub sig: String, 57 57 } 58 58 59 - pub async fn sign_op(rkey: &RotationKey, op: PlcOperation) -> anyhow::Result<SignedPlcOperation> { 59 + pub(crate) async fn sign_op(rkey: &RotationKey, op: PlcOperation) -> anyhow::Result<SignedPlcOperation> { 60 60 let bytes = serde_ipld_dagcbor::to_vec(&op).context("failed to encode op")?; 61 61 let bytes = rkey.sign(&bytes).context("failed to sign op")?; 62 62 ··· 64 64 } 65 65 66 66 /// Submit a PLC operation to the public directory. 67 - pub async fn submit(client: &Client, did: &str, op: &SignedPlcOperation) -> anyhow::Result<()> { 67 + pub(crate) async fn submit(client: &Client, did: &str, op: &SignedPlcOperation) -> anyhow::Result<()> { 68 68 debug!("submitting {} {}", did, serde_json::to_string(&op).unwrap()); 69 69 70 70 let res = client
+3 -3
src/storage.rs
··· 10 10 11 11 use crate::{Db, config::RepoConfig}; 12 12 13 - pub async fn open_store( 13 + pub(crate) async fn open_store( 14 14 config: &RepoConfig, 15 15 did: impl Into<String>, 16 16 ) -> Result<impl AsyncBlockStoreRead + AsyncBlockStoreWrite> { ··· 31 31 CarStore::open(f).await.context("failed to open car store") 32 32 } 33 33 34 - pub async fn open_repo_db( 34 + pub(crate) async fn open_repo_db( 35 35 config: &RepoConfig, 36 36 db: &Db, 37 37 did: impl Into<String>, ··· 51 51 open_repo(config, did, Cid::from_str(&cid).unwrap()).await 52 52 } 53 53 54 - pub async fn open_repo( 54 + pub(crate) async fn open_repo( 55 55 config: &RepoConfig, 56 56 did: impl Into<String>, 57 57 cid: Cid,