···11-// https://stackoverflow.com/questions/73077972/how-to-deploy-app-service-with-managed-ssl-certificate-using-arm
22-//
33-// TLDR: Azure requires a circular dependency in order to define an app service with a custom domain with SSL enabled.
44-// Terrific user experience. Really makes me love using Azure in my free time.
55-param webAppName string
66-param location string
77-param appServicePlanResourceId string
88-param customHostnames array
99-1010-// Managed certificates can only be created once the hostname is added to the web app.
1111-resource certificates 'Microsoft.Web/certificates@2022-03-01' = [for (fqdn, i) in customHostnames: {
1212- name: '${fqdn}-${webAppName}'
1313- location: location
1414- properties: {
1515- serverFarmId: appServicePlanResourceId
1616- canonicalName: fqdn
1717- }
1818-}]
1919-2020-// sslState and thumbprint can only be set once the managed certificate is created
2121-@batchSize(1)
2222-resource customHostname 'Microsoft.web/sites/hostnameBindings@2019-08-01' = [for (fqdn, i) in customHostnames: {
2323- name: '${webAppName}/${fqdn}'
2424- properties: {
2525- siteName: webAppName
2626- hostNameType: 'Verified'
2727- sslState: 'SniEnabled'
2828- thumbprint: certificates[i].properties.thumbprint
2929- }
3030-}]
···22//! blacksky-algorithms/rsky is licensed under the Apache License 2.0
33//!
44//! Modified for SQLite backend
55+use crate::schema::pds::account::dsl as AccountSchema;
66+use crate::schema::pds::actor::dsl as ActorSchema;
57use anyhow::Result;
68use chrono::DateTime;
79use chrono::offset::Utc as UtcOffset;
···1517 AccountStatus, ActorAccount, ActorJoinAccount, AvailabilityFlags, FormattedAccountStatus,
1618 GetAccountAdminStatusOutput, format_account_status,
1719};
1818-use rsky_pds::schema::pds::account::dsl as AccountSchema;
1919-use rsky_pds::schema::pds::actor::dsl as ActorSchema;
2020use std::ops::Add;
2121use std::time::SystemTime;
2222use thiserror::Error;
···253253 deadpool_diesel::sqlite::Object,
254254 >,
255255) -> Result<()> {
256256- use rsky_pds::schema::pds::email_token::dsl as EmailTokenSchema;
257257- use rsky_pds::schema::pds::refresh_token::dsl as RefreshTokenSchema;
258258- use rsky_pds::schema::pds::repo_root::dsl as RepoRootSchema;
256256+ use crate::schema::pds::email_token::dsl as EmailTokenSchema;
257257+ use crate::schema::pds::refresh_token::dsl as RefreshTokenSchema;
258258+ use crate::schema::pds::repo_root::dsl as RepoRootSchema;
259259260260 let did = did.to_owned();
261261 _ = db
···410410 deadpool_diesel::sqlite::Object,
411411 >,
412412) -> Result<()> {
413413- use rsky_pds::schema::pds::actor;
413413+ use crate::schema::pds::actor;
414414415415 let actor2 = diesel::alias!(actor as actor2);
416416
+7-7
src/account_manager/helpers/auth.rs
···2222 deadpool_diesel::sqlite::Object,
2323 >,
2424) -> Result<()> {
2525- use rsky_pds::schema::pds::refresh_token::dsl as RefreshTokenSchema;
2525+ use crate::schema::pds::refresh_token::dsl as RefreshTokenSchema;
26262727 let exp = from_micros_to_utc((payload.exp.as_millis() / 1000) as i64);
2828···5353 deadpool_diesel::sqlite::Object,
5454 >,
5555) -> Result<bool> {
5656- use rsky_pds::schema::pds::refresh_token::dsl as RefreshTokenSchema;
5656+ use crate::schema::pds::refresh_token::dsl as RefreshTokenSchema;
5757 db.get()
5858 .await?
5959 .interact(move |conn| {
···7474 deadpool_diesel::sqlite::Object,
7575 >,
7676) -> Result<bool> {
7777- use rsky_pds::schema::pds::refresh_token::dsl as RefreshTokenSchema;
7777+ use crate::schema::pds::refresh_token::dsl as RefreshTokenSchema;
7878 let did = did.to_owned();
7979 db.get()
8080 .await?
···9797 deadpool_diesel::sqlite::Object,
9898 >,
9999) -> Result<bool> {
100100- use rsky_pds::schema::pds::refresh_token::dsl as RefreshTokenSchema;
100100+ use crate::schema::pds::refresh_token::dsl as RefreshTokenSchema;
101101102102 let did = did.to_owned();
103103 let app_pass_name = app_pass_name.to_owned();
···122122 deadpool_diesel::sqlite::Object,
123123 >,
124124) -> Result<Option<models::RefreshToken>> {
125125- use rsky_pds::schema::pds::refresh_token::dsl as RefreshTokenSchema;
125125+ use crate::schema::pds::refresh_token::dsl as RefreshTokenSchema;
126126 let id = id.to_owned();
127127 db.get()
128128 .await?
···144144 deadpool_diesel::sqlite::Object,
145145 >,
146146) -> Result<()> {
147147- use rsky_pds::schema::pds::refresh_token::dsl as RefreshTokenSchema;
147147+ use crate::schema::pds::refresh_token::dsl as RefreshTokenSchema;
148148 let did = did.to_owned();
149149150150 db.get()
···175175 expires_at,
176176 next_id,
177177 } = opts;
178178- use rsky_pds::schema::pds::refresh_token::dsl as RefreshTokenSchema;
178178+ use crate::schema::pds::refresh_token::dsl as RefreshTokenSchema;
179179180180 drop(
181181 update(RefreshTokenSchema::refresh_token)
+5-5
src/account_manager/helpers/email_token.rs
···1717 deadpool_diesel::sqlite::Object,
1818 >,
1919) -> Result<String> {
2020- use rsky_pds::schema::pds::email_token::dsl as EmailTokenSchema;
2020+ use crate::schema::pds::email_token::dsl as EmailTokenSchema;
2121 let token = get_random_token().to_uppercase();
2222 let now = rsky_common::now();
2323···5656 >,
5757) -> Result<()> {
5858 let expiration_len = expiration_len.unwrap_or(MINUTE * 15);
5959- use rsky_pds::schema::pds::email_token::dsl as EmailTokenSchema;
5959+ use crate::schema::pds::email_token::dsl as EmailTokenSchema;
60606161 let did = did.to_owned();
6262 let token = token.to_owned();
···9696 >,
9797) -> Result<String> {
9898 let expiration_len = expiration_len.unwrap_or(MINUTE * 15);
9999- use rsky_pds::schema::pds::email_token::dsl as EmailTokenSchema;
9999+ use crate::schema::pds::email_token::dsl as EmailTokenSchema;
100100101101 let token = token.to_owned();
102102 let res = db
···210210 deadpool_diesel::sqlite::Object,
211211 >,
212212) -> Result<()> {
213213- use rsky_pds::schema::pds::email_token::dsl as EmailTokenSchema;
213213+ use crate::schema::pds::email_token::dsl as EmailTokenSchema;
214214 let did = did.to_owned();
215215 _ = db
216216 .get()
···233233 deadpool_diesel::sqlite::Object,
234234 >,
235235) -> Result<()> {
236236- use rsky_pds::schema::pds::email_token::dsl as EmailTokenSchema;
236236+ use crate::schema::pds::email_token::dsl as EmailTokenSchema;
237237238238 let did = did.to_owned();
239239 _ = db
+12-12
src/account_manager/helpers/invite.rs
···2323 deadpool_diesel::sqlite::Object,
2424 >,
2525) -> Result<()> {
2626- use rsky_pds::schema::pds::actor::dsl as ActorSchema;
2727- use rsky_pds::schema::pds::invite_code::dsl as InviteCodeSchema;
2828- use rsky_pds::schema::pds::invite_code_use::dsl as InviteCodeUseSchema;
2626+ use crate::schema::pds::actor::dsl as ActorSchema;
2727+ use crate::schema::pds::invite_code::dsl as InviteCodeSchema;
2828+ use crate::schema::pds::invite_code_use::dsl as InviteCodeUseSchema;
29293030 db.get().await?.interact(move |conn| {
3131 let invite: Option<models::InviteCode> = InviteCodeSchema::invite_code
···7272 >,
7373) -> Result<()> {
7474 if let Some(invite_code) = invite_code {
7575- use rsky_pds::schema::pds::invite_code_use::dsl as InviteCodeUseSchema;
7575+ use crate::schema::pds::invite_code_use::dsl as InviteCodeUseSchema;
76767777 _ = db
7878 .get()
···100100 deadpool_diesel::sqlite::Object,
101101 >,
102102) -> Result<()> {
103103- use rsky_pds::schema::pds::invite_code::dsl as InviteCodeSchema;
103103+ use crate::schema::pds::invite_code::dsl as InviteCodeSchema;
104104 let created_at = rsky_common::now();
105105106106 _ = db
···144144 deadpool_diesel::sqlite::Object,
145145 >,
146146) -> Result<Vec<CodeDetail>> {
147147- use rsky_pds::schema::pds::invite_code::dsl as InviteCodeSchema;
147147+ use crate::schema::pds::invite_code::dsl as InviteCodeSchema;
148148149149 let for_account = for_account.to_owned();
150150 let rows = db
···201201 deadpool_diesel::sqlite::Object,
202202 >,
203203) -> Result<Vec<CodeDetail>> {
204204- use rsky_pds::schema::pds::invite_code::dsl as InviteCodeSchema;
204204+ use crate::schema::pds::invite_code::dsl as InviteCodeSchema;
205205206206 let did = did.to_owned();
207207 let res: Vec<models::InviteCode> = db
···239239 deadpool_diesel::sqlite::Object,
240240 >,
241241) -> Result<BTreeMap<String, Vec<CodeUse>>> {
242242- use rsky_pds::schema::pds::invite_code_use::dsl as InviteCodeUseSchema;
242242+ use crate::schema::pds::invite_code_use::dsl as InviteCodeUseSchema;
243243244244 let mut uses: BTreeMap<String, Vec<CodeUse>> = BTreeMap::new();
245245 if !codes.is_empty() {
···282282 if dids.is_empty() {
283283 return Ok(BTreeMap::new());
284284 }
285285- use rsky_pds::schema::pds::invite_code::dsl as InviteCodeSchema;
286286- use rsky_pds::schema::pds::invite_code_use::dsl as InviteCodeUseSchema;
285285+ use crate::schema::pds::invite_code::dsl as InviteCodeSchema;
286286+ use crate::schema::pds::invite_code_use::dsl as InviteCodeUseSchema;
287287288288 let dids = dids.clone();
289289 let res: Vec<models::InviteCode> = db
···339339 deadpool_diesel::sqlite::Object,
340340 >,
341341) -> Result<()> {
342342- use rsky_pds::schema::pds::account::dsl as AccountSchema;
342342+ use crate::schema::pds::account::dsl as AccountSchema;
343343344344 let disabled: i16 = if disabled { 1 } else { 0 };
345345 let did = did.to_owned();
···364364 deadpool_diesel::sqlite::Object,
365365 >,
366366) -> Result<()> {
367367- use rsky_pds::schema::pds::invite_code::dsl as InviteCodeSchema;
367367+ use crate::schema::pds::invite_code::dsl as InviteCodeSchema;
368368369369 let DisableInviteCodesOpts { codes, accounts } = opts;
370370 if !codes.is_empty() {
+6-6
src/account_manager/helpers/password.rs
···2121 deadpool_diesel::sqlite::Object,
2222 >,
2323) -> Result<bool> {
2424- use rsky_pds::schema::pds::account::dsl as AccountSchema;
2424+ use crate::schema::pds::account::dsl as AccountSchema;
25252626 let did = did.to_owned();
2727 let found = db
···5151 deadpool_diesel::sqlite::Object,
5252 >,
5353) -> Result<Option<String>> {
5454- use rsky_pds::schema::pds::app_password::dsl as AppPasswordSchema;
5454+ use crate::schema::pds::app_password::dsl as AppPasswordSchema;
55555656 let did = did.to_owned();
5757 let password = password.to_owned();
···9191 let password = chunks.join("-");
9292 let password_encrypted = hash_app_password(&did, &password).await?;
93939494- use rsky_pds::schema::pds::app_password::dsl as AppPasswordSchema;
9494+ use crate::schema::pds::app_password::dsl as AppPasswordSchema;
95959696 let created_at = now();
9797···129129 deadpool_diesel::sqlite::Object,
130130 >,
131131) -> Result<Vec<(String, String)>> {
132132- use rsky_pds::schema::pds::app_password::dsl as AppPasswordSchema;
132132+ use crate::schema::pds::app_password::dsl as AppPasswordSchema;
133133134134 let did = did.to_owned();
135135 db.get()
···151151 deadpool_diesel::sqlite::Object,
152152 >,
153153) -> Result<()> {
154154- use rsky_pds::schema::pds::account::dsl as AccountSchema;
154154+ use crate::schema::pds::account::dsl as AccountSchema;
155155156156 db.get()
157157 .await?
···174174 deadpool_diesel::sqlite::Object,
175175 >,
176176) -> Result<()> {
177177- use rsky_pds::schema::pds::app_password::dsl as AppPasswordSchema;
177177+ use crate::schema::pds::app_password::dsl as AppPasswordSchema;
178178179179 let did = did.to_owned();
180180 let name = name.to_owned();
+1-1
src/account_manager/helpers/repo.rs
···1616 >,
1717) -> Result<()> {
1818 // @TODO balance risk of a race in the case of a long retry
1919- use rsky_pds::schema::pds::repo_root::dsl as RepoRootSchema;
1919+ use crate::schema::pds::repo_root::dsl as RepoRootSchema;
20202121 let now = rsky_common::now();
2222
+15-15
src/actor_store/blob.rs
···67676868 /// Get metadata for a blob by CID
6969 pub async fn get_blob_metadata(&self, cid: Cid) -> Result<GetBlobMetadataOutput> {
7070- use rsky_pds::schema::pds::blob::dsl as BlobSchema;
7070+ use crate::schema::pds::blob::dsl as BlobSchema;
71717272 let did = self.did.clone();
7373 let found = self
···112112113113 /// Get all records that reference a specific blob
114114 pub async fn get_records_for_blob(&self, cid: Cid) -> Result<Vec<String>> {
115115- use rsky_pds::schema::pds::record_blob::dsl as RecordBlobSchema;
115115+ use crate::schema::pds::record_blob::dsl as RecordBlobSchema;
116116117117 let did = self.did.clone();
118118 let res = self
···169169170170 /// Track a blob that hasn't been associated with any records yet
171171 pub async fn track_untethered_blob(&self, metadata: BlobMetadata) -> Result<BlobRef> {
172172- use rsky_pds::schema::pds::blob::dsl as BlobSchema;
172172+ use crate::schema::pds::blob::dsl as BlobSchema;
173173174174 let did = self.did.clone();
175175 self.db.get().await?.interact(move |conn| {
···254254255255 /// Delete blobs that are no longer referenced by any records
256256 pub async fn delete_dereferenced_blobs(&self, writes: Vec<PreparedWrite>) -> Result<()> {
257257- use rsky_pds::schema::pds::blob::dsl as BlobSchema;
258258- use rsky_pds::schema::pds::record_blob::dsl as RecordBlobSchema;
257257+ use crate::schema::pds::blob::dsl as BlobSchema;
258258+ use crate::schema::pds::record_blob::dsl as RecordBlobSchema;
259259260260 // Extract URIs
261261 let uris: Vec<String> = writes
···386386387387 /// Verify a blob and make it permanent
388388 pub async fn verify_blob_and_make_permanent(&self, blob: PreparedBlobRef) -> Result<()> {
389389- use rsky_pds::schema::pds::blob::dsl as BlobSchema;
389389+ use crate::schema::pds::blob::dsl as BlobSchema;
390390391391 let found = self
392392 .db
···433433434434 /// Associate a blob with a record
435435 pub async fn associate_blob(&self, blob: PreparedBlobRef, record_uri: String) -> Result<()> {
436436- use rsky_pds::schema::pds::record_blob::dsl as RecordBlobSchema;
436436+ use crate::schema::pds::record_blob::dsl as RecordBlobSchema;
437437438438 let cid = blob.cid.to_string();
439439 let did = self.did.clone();
···460460461461 /// Count all blobs for this actor
462462 pub async fn blob_count(&self) -> Result<i64> {
463463- use rsky_pds::schema::pds::blob::dsl as BlobSchema;
463463+ use crate::schema::pds::blob::dsl as BlobSchema;
464464465465 let did = self.did.clone();
466466 self.db
···479479480480 /// Count blobs associated with records
481481 pub async fn record_blob_count(&self) -> Result<i64> {
482482- use rsky_pds::schema::pds::record_blob::dsl as RecordBlobSchema;
482482+ use crate::schema::pds::record_blob::dsl as RecordBlobSchema;
483483484484 let did = self.did.clone();
485485 self.db
···501501 &self,
502502 opts: ListMissingBlobsOpts,
503503 ) -> Result<Vec<ListMissingBlobsRefRecordBlob>> {
504504- use rsky_pds::schema::pds::blob::dsl as BlobSchema;
505505- use rsky_pds::schema::pds::record_blob::dsl as RecordBlobSchema;
504504+ use crate::schema::pds::blob::dsl as BlobSchema;
505505+ use crate::schema::pds::record_blob::dsl as RecordBlobSchema;
506506507507 let did = self.did.clone();
508508 self.db
···563563564564 /// List all blobs with optional filtering
565565 pub async fn list_blobs(&self, opts: ListBlobsOpts) -> Result<Vec<String>> {
566566- use rsky_pds::schema::pds::record::dsl as RecordSchema;
567567- use rsky_pds::schema::pds::record_blob::dsl as RecordBlobSchema;
566566+ use crate::schema::pds::record::dsl as RecordSchema;
567567+ use crate::schema::pds::record_blob::dsl as RecordBlobSchema;
568568569569 let ListBlobsOpts {
570570 since,
···617617618618 /// Get the takedown status of a blob
619619 pub async fn get_blob_takedown_status(&self, cid: Cid) -> Result<Option<StatusAttr>> {
620620- use rsky_pds::schema::pds::blob::dsl as BlobSchema;
620620+ use crate::schema::pds::blob::dsl as BlobSchema;
621621622622 self.db
623623 .get()
···653653654654 /// Update the takedown status of a blob
655655 pub async fn update_blob_takedown_status(&self, blob: Cid, takedown: StatusAttr) -> Result<()> {
656656- use rsky_pds::schema::pds::blob::dsl as BlobSchema;
656656+ use crate::schema::pds::blob::dsl as BlobSchema;
657657658658 let takedown_ref: Option<String> = match takedown.applied {
659659 true => takedown.r#ref.map_or_else(|| Some(now()), Some),
+2-2
src/actor_store/mod.rs
···460460461461 pub async fn destroy(&mut self) -> Result<()> {
462462 let did: String = self.did.clone();
463463- use rsky_pds::schema::pds::blob::dsl as BlobSchema;
463463+ use crate::schema::pds::blob::dsl as BlobSchema;
464464465465 let blob_rows: Vec<String> = self
466466 .storage
···499499 return Ok(vec![]);
500500 }
501501 let did: String = self.did.clone();
502502- use rsky_pds::schema::pds::record::dsl as RecordSchema;
502502+ use crate::schema::pds::record::dsl as RecordSchema;
503503504504 let cid_strs: Vec<String> = cids.into_iter().map(|c| c.to_string()).collect();
505505 let touched_uri_strs: Vec<String> = touched_uris.iter().map(|t| t.to_string()).collect();
+2-2
src/actor_store/preference.rs
···3636 namespace: Option<String>,
3737 scope: AuthScope,
3838 ) -> Result<Vec<RefPreferences>> {
3939- use rsky_pds::schema::pds::account_pref::dsl as AccountPrefSchema;
3939+ use crate::schema::pds::account_pref::dsl as AccountPrefSchema;
40404141 let did = self.did.clone();
4242 self.db
···9999 bail!("Do not have authorization to set preferences.");
100100 }
101101 // get all current prefs for user and prep new pref rows
102102- use rsky_pds::schema::pds::account_pref::dsl as AccountPrefSchema;
102102+ use crate::schema::pds::account_pref::dsl as AccountPrefSchema;
103103 let all_prefs = AccountPrefSchema::account_pref
104104 .filter(AccountPrefSchema::did.eq(&did))
105105 .select(AccountPref::as_select())
+17-17
src/actor_store/record.rs
···43434444 /// Count the total number of records.
4545 pub(crate) async fn record_count(&mut self) -> Result<i64> {
4646- use rsky_pds::schema::pds::record::dsl::*;
4646+ use crate::schema::pds::record::dsl::*;
47474848 let other_did = self.did.clone();
4949 self.db
···59596060 /// List all collections in the repository.
6161 pub(crate) async fn list_collections(&self) -> Result<Vec<String>> {
6262- use rsky_pds::schema::pds::record::dsl::*;
6262+ use crate::schema::pds::record::dsl::*;
63636464 let other_did = self.did.clone();
6565 self.db
···9090 rkey_end: Option<String>,
9191 include_soft_deleted: Option<bool>,
9292 ) -> Result<Vec<RecordsForCollection>> {
9393- use rsky_pds::schema::pds::record::dsl as RecordSchema;
9494- use rsky_pds::schema::pds::repo_block::dsl as RepoBlockSchema;
9393+ use crate::schema::pds::record::dsl as RecordSchema;
9494+ use crate::schema::pds::repo_block::dsl as RepoBlockSchema;
95959696 let include_soft_deleted: bool = include_soft_deleted.unwrap_or(false);
9797 let mut builder = RecordSchema::record
···149149 cid: Option<String>,
150150 include_soft_deleted: Option<bool>,
151151 ) -> Result<Option<GetRecord>> {
152152- use rsky_pds::schema::pds::record::dsl as RecordSchema;
153153- use rsky_pds::schema::pds::repo_block::dsl as RepoBlockSchema;
152152+ use crate::schema::pds::record::dsl as RecordSchema;
153153+ use crate::schema::pds::repo_block::dsl as RepoBlockSchema;
154154155155 let include_soft_deleted: bool = include_soft_deleted.unwrap_or(false);
156156 let mut builder = RecordSchema::record
···191191 cid: Option<String>,
192192 include_soft_deleted: Option<bool>,
193193 ) -> Result<bool> {
194194- use rsky_pds::schema::pds::record::dsl as RecordSchema;
194194+ use crate::schema::pds::record::dsl as RecordSchema;
195195196196 let include_soft_deleted: bool = include_soft_deleted.unwrap_or(false);
197197 let mut builder = RecordSchema::record
···219219 &self,
220220 uri: String,
221221 ) -> Result<Option<StatusAttr>> {
222222- use rsky_pds::schema::pds::record::dsl as RecordSchema;
222222+ use crate::schema::pds::record::dsl as RecordSchema;
223223224224 let res = self
225225 .db
···257257258258 /// Get the current CID for a record URI.
259259 pub(crate) async fn get_current_record_cid(&self, uri: String) -> Result<Option<Cid>> {
260260- use rsky_pds::schema::pds::record::dsl as RecordSchema;
260260+ use crate::schema::pds::record::dsl as RecordSchema;
261261262262 let res = self
263263 .db
···286286 path: String,
287287 link_to: String,
288288 ) -> Result<Vec<Record>> {
289289- use rsky_pds::schema::pds::backlink::dsl as BacklinkSchema;
290290- use rsky_pds::schema::pds::record::dsl as RecordSchema;
289289+ use crate::schema::pds::backlink::dsl as BacklinkSchema;
290290+ use crate::schema::pds::record::dsl as RecordSchema;
291291292292 let res = self
293293 .db
···385385 bail!("Expected indexed URI to contain a record key")
386386 }
387387388388- use rsky_pds::schema::pds::record::dsl as RecordSchema;
388388+ use crate::schema::pds::record::dsl as RecordSchema;
389389390390 // Track current version of record
391391 let (record, uri) = self
···426426 #[tracing::instrument(skip_all)]
427427 pub(crate) async fn delete_record(&self, uri: &AtUri) -> Result<()> {
428428 tracing::debug!("@LOG DEBUG RecordReader::delete_record, deleting indexed record {uri}");
429429- use rsky_pds::schema::pds::backlink::dsl as BacklinkSchema;
430430- use rsky_pds::schema::pds::record::dsl as RecordSchema;
429429+ use crate::schema::pds::backlink::dsl as BacklinkSchema;
430430+ use crate::schema::pds::record::dsl as RecordSchema;
431431 let uri = uri.to_string();
432432 self.db
433433 .get()
···450450451451 /// Remove backlinks for a URI.
452452 pub(crate) async fn remove_backlinks_by_uri(&self, uri: &AtUri) -> Result<()> {
453453- use rsky_pds::schema::pds::backlink::dsl as BacklinkSchema;
453453+ use crate::schema::pds::backlink::dsl as BacklinkSchema;
454454 let uri = uri.to_string();
455455 self.db
456456 .get()
···470470 if backlinks.is_empty() {
471471 Ok(())
472472 } else {
473473- use rsky_pds::schema::pds::backlink::dsl as BacklinkSchema;
473473+ use crate::schema::pds::backlink::dsl as BacklinkSchema;
474474 self.db
475475 .get()
476476 .await?
···491491 uri: &AtUri,
492492 takedown: StatusAttr,
493493 ) -> Result<()> {
494494- use rsky_pds::schema::pds::record::dsl as RecordSchema;
494494+ use crate::schema::pds::record::dsl as RecordSchema;
495495496496 let takedown_ref: Option<String> = match takedown.applied {
497497 true => takedown
+10-10
src/actor_store/sql_repo.rs
···5353 let cid = *cid;
54545555 Box::pin(async move {
5656- use rsky_pds::schema::pds::repo_block::dsl as RepoBlockSchema;
5656+ use crate::schema::pds::repo_block::dsl as RepoBlockSchema;
5757 let cached = {
5858 let cache_guard = self.cache.read().await;
5959 cache_guard.get(cid).cloned()
···104104 let did: String = self.did.clone();
105105106106 Box::pin(async move {
107107- use rsky_pds::schema::pds::repo_block::dsl as RepoBlockSchema;
107107+ use crate::schema::pds::repo_block::dsl as RepoBlockSchema;
108108 let cached = {
109109 let mut cache_guard = self.cache.write().await;
110110 cache_guard.get_many(cids)?
···202202 let did: String = self.did.clone();
203203 let bytes_cloned = bytes.clone();
204204 Box::pin(async move {
205205- use rsky_pds::schema::pds::repo_block::dsl as RepoBlockSchema;
205205+ use crate::schema::pds::repo_block::dsl as RepoBlockSchema;
206206207207 _ = self
208208 .db
···235235 let did: String = self.did.clone();
236236237237 Box::pin(async move {
238238- use rsky_pds::schema::pds::repo_block::dsl as RepoBlockSchema;
238238+ use crate::schema::pds::repo_block::dsl as RepoBlockSchema;
239239240240 let blocks: Vec<RepoBlock> = to_put
241241 .map
···277277 let now: String = self.now.clone();
278278279279 Box::pin(async move {
280280- use rsky_pds::schema::pds::repo_root::dsl as RepoRootSchema;
280280+ use crate::schema::pds::repo_root::dsl as RepoRootSchema;
281281282282 let is_create = is_create.unwrap_or(false);
283283 if is_create {
···381381 let did: String = self.did.clone();
382382 let since = since.clone();
383383 let cursor = cursor.clone();
384384- use rsky_pds::schema::pds::repo_block::dsl as RepoBlockSchema;
384384+ use crate::schema::pds::repo_block::dsl as RepoBlockSchema;
385385386386 Ok(self
387387 .db
···418418419419 pub async fn count_blocks(&self) -> Result<i64> {
420420 let did: String = self.did.clone();
421421- use rsky_pds::schema::pds::repo_block::dsl as RepoBlockSchema;
421421+ use crate::schema::pds::repo_block::dsl as RepoBlockSchema;
422422423423 let res = self
424424 .db
···439439 /// Proactively cache all blocks from a particular commit (to prevent multiple roundtrips)
440440 pub async fn cache_rev(&mut self, rev: String) -> Result<()> {
441441 let did: String = self.did.clone();
442442- use rsky_pds::schema::pds::repo_block::dsl as RepoBlockSchema;
442442+ use crate::schema::pds::repo_block::dsl as RepoBlockSchema;
443443444444 let result: Vec<(String, Vec<u8>)> = self
445445 .db
···465465 return Ok(());
466466 }
467467 let did: String = self.did.clone();
468468- use rsky_pds::schema::pds::repo_block::dsl as RepoBlockSchema;
468468+ use crate::schema::pds::repo_block::dsl as RepoBlockSchema;
469469470470 let cid_strings: Vec<String> = cids.into_iter().map(|c| c.to_string()).collect();
471471 _ = self
···483483484484 pub async fn get_root_detailed(&self) -> Result<CidAndRev> {
485485 let did: String = self.did.clone();
486486- use rsky_pds::schema::pds::repo_root::dsl as RepoRootSchema;
486486+ use crate::schema::pds::repo_root::dsl as RepoRootSchema;
487487488488 let res = self
489489 .db
+2-2
src/auth.rs
···130130131131 // Extract subject (DID)
132132 if let Some(did) = claims.get("sub").and_then(serde_json::Value::as_str) {
133133- use rsky_pds::schema::pds::account::dsl as AccountSchema;
133133+ use crate::schema::pds::account::dsl as AccountSchema;
134134 let did_clone = did.to_owned();
135135136136 let _did = state
···395395396396 // Extract subject (DID) from access token
397397 if let Some(did) = claims.get("sub").and_then(|v| v.as_str()) {
398398- use rsky_pds::schema::pds::account::dsl as AccountSchema;
398398+ use crate::schema::pds::account::dsl as AccountSchema;
399399400400 let did_clone = did.to_owned();
401401
+460
src/lib.rs
···11+//! PDS implementation.
22+mod account_manager;
33+mod actor_endpoints;
44+mod actor_store;
55+mod auth;
66+mod config;
77+mod db;
88+mod did;
99+mod endpoints;
1010+pub mod error;
1111+mod firehose;
1212+mod metrics;
1313+mod mmap;
1414+mod oauth;
1515+mod plc;
1616+mod schema;
1717+mod service_proxy;
1818+#[cfg(test)]
1919+mod tests;
2020+2121+use anyhow::{Context as _, anyhow};
2222+use atrium_api::types::string::Did;
2323+use atrium_crypto::keypair::{Export as _, Secp256k1Keypair};
2424+use auth::AuthenticatedUser;
2525+use axum::{
2626+ Router,
2727+ body::Body,
2828+ extract::{FromRef, Request, State},
2929+ http::{self, HeaderMap, Response, StatusCode, Uri},
3030+ response::IntoResponse,
3131+ routing::get,
3232+};
3333+use azure_core::credentials::TokenCredential;
3434+use clap::Parser;
3535+use clap_verbosity_flag::{InfoLevel, Verbosity, log::LevelFilter};
3636+use config::AppConfig;
3737+use db::establish_pool;
3838+use deadpool_diesel::sqlite::Pool;
3939+use diesel::prelude::*;
4040+use diesel_migrations::{EmbeddedMigrations, embed_migrations};
4141+pub use error::Error;
4242+use figment::{Figment, providers::Format as _};
4343+use firehose::FirehoseProducer;
4444+use http_cache_reqwest::{CacheMode, HttpCacheOptions, MokaManager};
4545+use rand::Rng as _;
4646+use serde::{Deserialize, Serialize};
4747+use service_proxy::service_proxy;
4848+use std::{
4949+ net::{IpAddr, Ipv4Addr, SocketAddr},
5050+ path::PathBuf,
5151+ str::FromStr as _,
5252+ sync::Arc,
5353+};
5454+use tokio::net::TcpListener;
5555+use tower_http::{cors::CorsLayer, trace::TraceLayer};
5656+use tracing::{info, warn};
5757+use uuid::Uuid;
5858+5959+/// The application user agent. Concatenates the package name and version. e.g. `bluepds/0.0.0`.
6060+pub const APP_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"),);
6161+6262+/// Embedded migrations
6363+pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!("./migrations");
6464+6565+/// The application-wide result type.
6666+pub type Result<T> = std::result::Result<T, Error>;
6767+/// The reqwest client type with middleware.
6868+pub type Client = reqwest_middleware::ClientWithMiddleware;
6969+/// The Azure credential type.
7070+pub type Cred = Arc<dyn TokenCredential>;
7171+7272+#[expect(
7373+ clippy::arbitrary_source_item_ordering,
7474+ reason = "serialized data might be structured"
7575+)]
7676+#[derive(Serialize, Deserialize, Debug, Clone)]
7777+/// The key data structure.
7878+struct KeyData {
7979+ /// Primary signing key for all repo operations.
8080+ skey: Vec<u8>,
8181+ /// Primary signing (rotation) key for all PLC operations.
8282+ rkey: Vec<u8>,
8383+}
8484+8585+// FIXME: We should use P256Keypair instead. SecP256K1 is primarily used for cryptocurrencies,
8686+// and the implementations of this algorithm are much more limited as compared to P256.
8787+//
8888+// Reference: https://soatok.blog/2022/05/19/guidance-for-choosing-an-elliptic-curve-signature-algorithm-in-2022/
8989+#[derive(Clone)]
9090+/// The signing key for PLC/DID operations.
9191+pub struct SigningKey(Arc<Secp256k1Keypair>);
9292+#[derive(Clone)]
9393+/// The rotation key for PLC operations.
9494+pub struct RotationKey(Arc<Secp256k1Keypair>);
9595+9696+impl std::ops::Deref for SigningKey {
9797+ type Target = Secp256k1Keypair;
9898+9999+ fn deref(&self) -> &Self::Target {
100100+ &self.0
101101+ }
102102+}
103103+104104+impl SigningKey {
105105+ /// Import from a private key.
106106+ pub fn import(key: &[u8]) -> Result<Self> {
107107+ let key = Secp256k1Keypair::import(key).context("failed to import signing key")?;
108108+ Ok(Self(Arc::new(key)))
109109+ }
110110+}
111111+112112+impl std::ops::Deref for RotationKey {
113113+ type Target = Secp256k1Keypair;
114114+115115+ fn deref(&self) -> &Self::Target {
116116+ &self.0
117117+ }
118118+}
119119+120120+#[derive(Parser, Debug, Clone)]
121121+/// Command line arguments.
122122+pub struct Args {
123123+ /// Path to the configuration file
124124+ #[arg(short, long, default_value = "default.toml")]
125125+ pub config: PathBuf,
126126+ /// The verbosity level.
127127+ #[command(flatten)]
128128+ pub verbosity: Verbosity<InfoLevel>,
129129+}
130130+131131+pub struct ActorPools {
132132+ pub repo: Pool,
133133+ pub blob: Pool,
134134+}
135135+136136+impl Clone for ActorPools {
137137+ fn clone(&self) -> Self {
138138+ Self {
139139+ repo: self.repo.clone(),
140140+ blob: self.blob.clone(),
141141+ }
142142+ }
143143+}
144144+145145+#[expect(clippy::arbitrary_source_item_ordering, reason = "arbitrary")]
146146+#[derive(Clone, FromRef)]
147147+pub struct AppState {
148148+ /// The application configuration.
149149+ pub config: AppConfig,
150150+ /// The Azure credential.
151151+ pub cred: Cred,
152152+ /// The main database connection pool. Used for common PDS data, like invite codes.
153153+ pub db: Pool,
154154+ /// Actor-specific database connection pools. Hashed by DID.
155155+ pub db_actors: std::collections::HashMap<String, ActorPools>,
156156+157157+ /// The HTTP client with middleware.
158158+ pub client: Client,
159159+ /// The simple HTTP client.
160160+ pub simple_client: reqwest::Client,
161161+ /// The firehose producer.
162162+ pub firehose: FirehoseProducer,
163163+164164+ /// The signing key.
165165+ pub signing_key: SigningKey,
166166+ /// The rotation key.
167167+ pub rotation_key: RotationKey,
168168+}
169169+170170+/// The index (/) route.
171171+async fn index() -> impl IntoResponse {
172172+ r"
173173+ __ __
174174+ /\ \__ /\ \__
175175+ __ \ \ ,_\ _____ _ __ ___\ \ ,_\ ___
176176+ /'__'\ \ \ \/ /\ '__'\/\''__\/ __'\ \ \/ / __'\
177177+ /\ \L\.\_\ \ \_\ \ \L\ \ \ \//\ \L\ \ \ \_/\ \L\ \
178178+ \ \__/.\_\\ \__\\ \ ,__/\ \_\\ \____/\ \__\ \____/
179179+ \/__/\/_/ \/__/ \ \ \/ \/_/ \/___/ \/__/\/___/
180180+ \ \_\
181181+ \/_/
182182+183183+184184+This is an AT Protocol Personal Data Server (aka, an atproto PDS)
185185+186186+Most API routes are under /xrpc/
187187+188188+ Code: https://github.com/DrChat/bluepds
189189+ Protocol: https://atproto.com
190190+ "
191191+}
192192+193193+/// The main application entry point.
194194+#[expect(
195195+ clippy::cognitive_complexity,
196196+ clippy::too_many_lines,
197197+ unused_qualifications,
198198+ reason = "main function has high complexity"
199199+)]
200200+pub async fn run() -> anyhow::Result<()> {
201201+ let args = Args::parse();
202202+203203+ // Set up trace logging to console and account for the user-provided verbosity flag.
204204+ if args.verbosity.log_level_filter() != LevelFilter::Off {
205205+ let lvl = match args.verbosity.log_level_filter() {
206206+ LevelFilter::Error => tracing::Level::ERROR,
207207+ LevelFilter::Warn => tracing::Level::WARN,
208208+ LevelFilter::Info | LevelFilter::Off => tracing::Level::INFO,
209209+ LevelFilter::Debug => tracing::Level::DEBUG,
210210+ LevelFilter::Trace => tracing::Level::TRACE,
211211+ };
212212+ tracing_subscriber::fmt().with_max_level(lvl).init();
213213+ }
214214+215215+ if !args.config.exists() {
216216+ // Throw up a warning if the config file does not exist.
217217+ //
218218+ // This is not fatal because users can specify all configuration settings via
219219+ // the environment, but the most likely scenario here is that a user accidentally
220220+ // omitted the config file for some reason (e.g. forgot to mount it into Docker).
221221+ warn!(
222222+ "configuration file {} does not exist",
223223+ args.config.display()
224224+ );
225225+ }
226226+227227+ // Read and parse the user-provided configuration.
228228+ let config: AppConfig = Figment::new()
229229+ .admerge(figment::providers::Toml::file(args.config))
230230+ .admerge(figment::providers::Env::prefixed("BLUEPDS_"))
231231+ .extract()
232232+ .context("failed to load configuration")?;
233233+234234+ if config.test {
235235+ warn!("BluePDS starting up in TEST mode.");
236236+ warn!("This means the application will not federate with the rest of the network.");
237237+ warn!(
238238+ "If you want to turn this off, either set `test` to false in the config or define `BLUEPDS_TEST = false`"
239239+ );
240240+ }
241241+242242+ // Initialize metrics reporting.
243243+ metrics::setup(config.metrics.as_ref()).context("failed to set up metrics exporter")?;
244244+245245+ // Create a reqwest client that will be used for all outbound requests.
246246+ let simple_client = reqwest::Client::builder()
247247+ .user_agent(APP_USER_AGENT)
248248+ .build()
249249+ .context("failed to build requester client")?;
250250+ let client = reqwest_middleware::ClientBuilder::new(simple_client.clone())
251251+ .with(http_cache_reqwest::Cache(http_cache_reqwest::HttpCache {
252252+ mode: CacheMode::Default,
253253+ manager: MokaManager::default(),
254254+ options: HttpCacheOptions::default(),
255255+ }))
256256+ .build();
257257+258258+ tokio::fs::create_dir_all(&config.key.parent().context("should have parent")?)
259259+ .await
260260+ .context("failed to create key directory")?;
261261+262262+ // Check if crypto keys exist. If not, create new ones.
263263+ let (skey, rkey) = if let Ok(f) = std::fs::File::open(&config.key) {
264264+ let keys: KeyData = serde_ipld_dagcbor::from_reader(std::io::BufReader::new(f))
265265+ .context("failed to deserialize crypto keys")?;
266266+267267+ let skey = Secp256k1Keypair::import(&keys.skey).context("failed to import signing key")?;
268268+ let rkey = Secp256k1Keypair::import(&keys.rkey).context("failed to import rotation key")?;
269269+270270+ (SigningKey(Arc::new(skey)), RotationKey(Arc::new(rkey)))
271271+ } else {
272272+ info!("signing keys not found, generating new ones");
273273+274274+ let skey = Secp256k1Keypair::create(&mut rand::thread_rng());
275275+ let rkey = Secp256k1Keypair::create(&mut rand::thread_rng());
276276+277277+ let keys = KeyData {
278278+ skey: skey.export(),
279279+ rkey: rkey.export(),
280280+ };
281281+282282+ let mut f = std::fs::File::create(&config.key).context("failed to create key file")?;
283283+ serde_ipld_dagcbor::to_writer(&mut f, &keys).context("failed to serialize crypto keys")?;
284284+285285+ (SigningKey(Arc::new(skey)), RotationKey(Arc::new(rkey)))
286286+ };
287287+288288+ tokio::fs::create_dir_all(&config.repo.path).await?;
289289+ tokio::fs::create_dir_all(&config.plc.path).await?;
290290+ tokio::fs::create_dir_all(&config.blob.path).await?;
291291+292292+ let cred = azure_identity::DefaultAzureCredential::new()
293293+ .context("failed to create Azure credential")?;
294294+295295+ // Create a database connection manager and pool for the main database.
296296+ let pool =
297297+ establish_pool(&config.db).context("failed to establish database connection pool")?;
298298+ // Create a dictionary of database connection pools for each actor.
299299+ let mut actor_pools = std::collections::HashMap::new();
300300+ // let mut actor_blob_pools = std::collections::HashMap::new();
301301+ // We'll determine actors by looking in the data/repo dir for .db files.
302302+ let mut actor_dbs = tokio::fs::read_dir(&config.repo.path)
303303+ .await
304304+ .context("failed to read repo directory")?;
305305+ while let Some(entry) = actor_dbs
306306+ .next_entry()
307307+ .await
308308+ .context("failed to read repo dir")?
309309+ {
310310+ let path = entry.path();
311311+ if path.extension().and_then(|s| s.to_str()) == Some("db") {
312312+ let did_path = path
313313+ .file_stem()
314314+ .and_then(|s| s.to_str())
315315+ .context("failed to get actor DID")?;
316316+ let did = Did::from_str(&format!("did:plc:{}", did_path))
317317+ .expect("should be able to parse actor DID");
318318+319319+ // Create a new database connection manager and pool for the actor.
320320+ // The path for the SQLite connection needs to look like "sqlite://data/repo/<actor>.db"
321321+ let path_repo = format!("sqlite://{}", did_path);
322322+ let actor_repo_pool =
323323+ establish_pool(&path_repo).context("failed to create database connection pool")?;
324324+ // Create a new database connection manager and pool for the actor blobs.
325325+ // The path for the SQLite connection needs to look like "sqlite://data/blob/<actor>.db"
326326+ let path_blob = path_repo.replace("repo", "blob");
327327+ let actor_blob_pool =
328328+ establish_pool(&path_blob).context("failed to create database connection pool")?;
329329+ drop(actor_pools.insert(
330330+ did.to_string(),
331331+ ActorPools {
332332+ repo: actor_repo_pool,
333333+ blob: actor_blob_pool,
334334+ },
335335+ ));
336336+ }
337337+ }
338338+ // Apply pending migrations
339339+ // let conn = pool.get().await?;
340340+ // conn.run_pending_migrations(MIGRATIONS)
341341+ // .expect("should be able to run migrations");
342342+343343+ let (_fh, fhp) = firehose::spawn(client.clone(), config.clone());
344344+345345+ let addr = config
346346+ .listen_address
347347+ .unwrap_or(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 8000));
348348+349349+ let app = Router::new()
350350+ .route("/", get(index))
351351+ .merge(oauth::routes())
352352+ .nest(
353353+ "/xrpc",
354354+ endpoints::routes()
355355+ .merge(actor_endpoints::routes())
356356+ .fallback(service_proxy),
357357+ )
358358+ // .layer(RateLimitLayer::new(30, Duration::from_secs(30)))
359359+ .layer(CorsLayer::permissive())
360360+ .layer(TraceLayer::new_for_http())
361361+ .with_state(AppState {
362362+ cred,
363363+ config: config.clone(),
364364+ db: pool.clone(),
365365+ db_actors: actor_pools.clone(),
366366+ client: client.clone(),
367367+ simple_client,
368368+ firehose: fhp,
369369+ signing_key: skey,
370370+ rotation_key: rkey,
371371+ });
372372+373373+ info!("listening on {addr}");
374374+ info!("connect to: http://127.0.0.1:{}", addr.port());
375375+376376+ // Determine whether or not this was the first startup (i.e. no accounts exist and no invite codes were created).
377377+ // If so, create an invite code and share it via the console.
378378+ let conn = pool.get().await.context("failed to get db connection")?;
379379+380380+ #[derive(QueryableByName)]
381381+ struct TotalCount {
382382+ #[diesel(sql_type = diesel::sql_types::Integer)]
383383+ total_count: i32,
384384+ }
385385+386386+ let result = conn.interact(move |conn| {
387387+ diesel::sql_query(
388388+ "SELECT (SELECT COUNT(*) FROM accounts) + (SELECT COUNT(*) FROM invites) AS total_count",
389389+ )
390390+ .get_result::<TotalCount>(conn)
391391+ })
392392+ .await
393393+ .expect("should be able to query database")?;
394394+395395+ let c = result.total_count;
396396+397397+ #[expect(clippy::print_stdout)]
398398+ if c == 0 {
399399+ let uuid = Uuid::new_v4().to_string();
400400+401401+ let uuid_clone = uuid.clone();
402402+ _ = conn
403403+ .interact(move |conn| {
404404+ diesel::sql_query(
405405+ "INSERT INTO invites (id, did, count, created_at) VALUES (?, NULL, 1, datetime('now'))",
406406+ )
407407+ .bind::<diesel::sql_types::Text, _>(uuid_clone)
408408+ .execute(conn)
409409+ .context("failed to create new invite code")
410410+ .expect("should be able to create invite code")
411411+ })
412412+ .await
413413+ .expect("should be able to create invite code");
414414+415415+ // N.B: This is a sensitive message, so we're bypassing `tracing` here and
416416+ // logging it directly to console.
417417+ println!("=====================================");
418418+ println!(" FIRST STARTUP ");
419419+ println!("=====================================");
420420+ println!("Use this code to create an account:");
421421+ println!("{uuid}");
422422+ println!("=====================================");
423423+ }
424424+425425+ let listener = TcpListener::bind(&addr)
426426+ .await
427427+ .context("failed to bind address")?;
428428+429429+ // Serve the app, and request crawling from upstream relays.
430430+ let serve = tokio::spawn(async move {
431431+ axum::serve(listener, app.into_make_service())
432432+ .await
433433+ .context("failed to serve app")
434434+ });
435435+436436+ // Now that the app is live, request a crawl from upstream relays.
437437+ firehose::reconnect_relays(&client, &config).await;
438438+439439+ serve
440440+ .await
441441+ .map_err(Into::into)
442442+ .and_then(|r| r)
443443+ .context("failed to serve app")
444444+}
445445+446446+/// Creates an app router with the provided AppState.
447447+pub fn create_app(state: AppState) -> Router {
448448+ Router::new()
449449+ .route("/", get(index))
450450+ .merge(oauth::routes())
451451+ .nest(
452452+ "/xrpc",
453453+ endpoints::routes()
454454+ .merge(actor_endpoints::routes())
455455+ .fallback(service_proxy),
456456+ )
457457+ .layer(CorsLayer::permissive())
458458+ .layer(TraceLayer::new_for_http())
459459+ .with_state(state)
460460+}
+5-444
src/main.rs
···11-//! PDS implementation.
22-mod account_manager;
33-mod actor_endpoints;
44-mod actor_store;
55-mod auth;
66-mod config;
77-mod db;
88-mod did;
99-mod endpoints;
1010-mod error;
1111-mod firehose;
1212-mod metrics;
1313-mod mmap;
1414-mod oauth;
1515-mod plc;
1616-mod schema;
1717-mod service_proxy;
1818-#[cfg(test)]
1919-mod tests;
11+//! BluePDS binary entry point.
2022121-use anyhow::{Context as _, anyhow};
2222-use atrium_api::types::string::Did;
2323-use atrium_crypto::keypair::{Export as _, Secp256k1Keypair};
2424-use auth::AuthenticatedUser;
2525-use axum::{
2626- Router,
2727- body::Body,
2828- extract::{FromRef, Request, State},
2929- http::{self, HeaderMap, Response, StatusCode, Uri},
3030- response::IntoResponse,
3131- routing::get,
3232-};
3333-use azure_core::credentials::TokenCredential;
33+use anyhow::Context as _;
344use clap::Parser;
3535-use clap_verbosity_flag::{InfoLevel, Verbosity, log::LevelFilter};
3636-use config::AppConfig;
3737-use db::establish_pool;
3838-use deadpool_diesel::sqlite::Pool;
3939-use diesel::prelude::*;
4040-use diesel_migrations::{EmbeddedMigrations, embed_migrations};
4141-#[expect(clippy::pub_use, clippy::useless_attribute)]
4242-pub use error::Error;
4343-use figment::{Figment, providers::Format as _};
4444-use firehose::FirehoseProducer;
4545-use http_cache_reqwest::{CacheMode, HttpCacheOptions, MokaManager};
4646-use rand::Rng as _;
4747-use serde::{Deserialize, Serialize};
4848-use service_proxy::service_proxy;
4949-use std::{
5050- net::{IpAddr, Ipv4Addr, SocketAddr},
5151- path::PathBuf,
5252- str::FromStr as _,
5353- sync::Arc,
5454-};
5555-use tokio::net::TcpListener;
5656-use tower_http::{cors::CorsLayer, trace::TraceLayer};
5757-use tracing::{info, warn};
5858-use uuid::Uuid;
5959-6060-/// The application user agent. Concatenates the package name and version. e.g. `bluepds/0.0.0`.
6161-pub const APP_USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"),);
6262-6363-/// Embedded migrations
6464-pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!("./migrations");
6565-6666-/// The application-wide result type.
6767-pub type Result<T> = std::result::Result<T, Error>;
6868-/// The reqwest client type with middleware.
6969-pub type Client = reqwest_middleware::ClientWithMiddleware;
7070-/// The Azure credential type.
7171-pub type Cred = Arc<dyn TokenCredential>;
7272-7373-#[expect(
7474- clippy::arbitrary_source_item_ordering,
7575- reason = "serialized data might be structured"
7676-)]
7777-#[derive(Serialize, Deserialize, Debug, Clone)]
7878-/// The key data structure.
7979-struct KeyData {
8080- /// Primary signing key for all repo operations.
8181- skey: Vec<u8>,
8282- /// Primary signing (rotation) key for all PLC operations.
8383- rkey: Vec<u8>,
8484-}
8585-8686-// FIXME: We should use P256Keypair instead. SecP256K1 is primarily used for cryptocurrencies,
8787-// and the implementations of this algorithm are much more limited as compared to P256.
8888-//
8989-// Reference: https://soatok.blog/2022/05/19/guidance-for-choosing-an-elliptic-curve-signature-algorithm-in-2022/
9090-#[derive(Clone)]
9191-/// The signing key for PLC/DID operations.
9292-pub struct SigningKey(Arc<Secp256k1Keypair>);
9393-#[derive(Clone)]
9494-/// The rotation key for PLC operations.
9595-pub struct RotationKey(Arc<Secp256k1Keypair>);
9696-9797-impl std::ops::Deref for SigningKey {
9898- type Target = Secp256k1Keypair;
9999-100100- fn deref(&self) -> &Self::Target {
101101- &self.0
102102- }
103103-}
104104-105105-impl SigningKey {
106106- /// Import from a private key.
107107- pub fn import(key: &[u8]) -> Result<Self> {
108108- let key = Secp256k1Keypair::import(key).context("failed to import signing key")?;
109109- Ok(Self(Arc::new(key)))
110110- }
111111-}
112112-113113-impl std::ops::Deref for RotationKey {
114114- type Target = Secp256k1Keypair;
115115-116116- fn deref(&self) -> &Self::Target {
117117- &self.0
118118- }
119119-}
120120-121121-#[derive(Parser, Debug, Clone)]
122122-/// Command line arguments.
123123-struct Args {
124124- /// Path to the configuration file
125125- #[arg(short, long, default_value = "default.toml")]
126126- config: PathBuf,
127127- /// The verbosity level.
128128- #[command(flatten)]
129129- verbosity: Verbosity<InfoLevel>,
130130-}
131131-132132-struct ActorPools {
133133- repo: Pool,
134134- blob: Pool,
135135-}
136136-impl Clone for ActorPools {
137137- fn clone(&self) -> Self {
138138- Self {
139139- repo: self.repo.clone(),
140140- blob: self.blob.clone(),
141141- }
142142- }
143143-}
144144-145145-#[expect(clippy::arbitrary_source_item_ordering, reason = "arbitrary")]
146146-#[derive(Clone, FromRef)]
147147-struct AppState {
148148- /// The application configuration.
149149- config: AppConfig,
150150- /// The Azure credential.
151151- cred: Cred,
152152- /// The main database connection pool. Used for common PDS data, like invite codes.
153153- db: Pool,
154154- /// Actor-specific database connection pools. Hashed by DID.
155155- db_actors: std::collections::HashMap<String, ActorPools>,
156156-157157- /// The HTTP client with middleware.
158158- client: Client,
159159- /// The simple HTTP client.
160160- simple_client: reqwest::Client,
161161- /// The firehose producer.
162162- firehose: FirehoseProducer,
163163-164164- /// The signing key.
165165- signing_key: SigningKey,
166166- /// The rotation key.
167167- rotation_key: RotationKey,
168168-}
169169-170170-/// The index (/) route.
171171-async fn index() -> impl IntoResponse {
172172- r"
173173- __ __
174174- /\ \__ /\ \__
175175- __ \ \ ,_\ _____ _ __ ___\ \ ,_\ ___
176176- /'__'\ \ \ \/ /\ '__'\/\''__\/ __'\ \ \/ / __'\
177177- /\ \L\.\_\ \ \_\ \ \L\ \ \ \//\ \L\ \ \ \_/\ \L\ \
178178- \ \__/.\_\\ \__\\ \ ,__/\ \_\\ \____/\ \__\ \____/
179179- \/__/\/_/ \/__/ \ \ \/ \/_/ \/___/ \/__/\/___/
180180- \ \_\
181181- \/_/
182182-183183-184184-This is an AT Protocol Personal Data Server (aka, an atproto PDS)
185185-186186-Most API routes are under /xrpc/
187187-188188- Code: https://github.com/DrChat/bluepds
189189- Protocol: https://atproto.com
190190- "
191191-}
192192-193193-/// The main application entry point.
194194-#[expect(
195195- clippy::cognitive_complexity,
196196- clippy::too_many_lines,
197197- unused_qualifications,
198198- reason = "main function has high complexity"
199199-)]
200200-async fn run() -> anyhow::Result<()> {
201201- let args = Args::parse();
202202-203203- // Set up trace logging to console and account for the user-provided verbosity flag.
204204- if args.verbosity.log_level_filter() != LevelFilter::Off {
205205- let lvl = match args.verbosity.log_level_filter() {
206206- LevelFilter::Error => tracing::Level::ERROR,
207207- LevelFilter::Warn => tracing::Level::WARN,
208208- LevelFilter::Info | LevelFilter::Off => tracing::Level::INFO,
209209- LevelFilter::Debug => tracing::Level::DEBUG,
210210- LevelFilter::Trace => tracing::Level::TRACE,
211211- };
212212- tracing_subscriber::fmt().with_max_level(lvl).init();
213213- }
214214-215215- if !args.config.exists() {
216216- // Throw up a warning if the config file does not exist.
217217- //
218218- // This is not fatal because users can specify all configuration settings via
219219- // the environment, but the most likely scenario here is that a user accidentally
220220- // omitted the config file for some reason (e.g. forgot to mount it into Docker).
221221- warn!(
222222- "configuration file {} does not exist",
223223- args.config.display()
224224- );
225225- }
226226-227227- // Read and parse the user-provided configuration.
228228- let config: AppConfig = Figment::new()
229229- .admerge(figment::providers::Toml::file(args.config))
230230- .admerge(figment::providers::Env::prefixed("BLUEPDS_"))
231231- .extract()
232232- .context("failed to load configuration")?;
233233-234234- if config.test {
235235- warn!("BluePDS starting up in TEST mode.");
236236- warn!("This means the application will not federate with the rest of the network.");
237237- warn!(
238238- "If you want to turn this off, either set `test` to false in the config or define `BLUEPDS_TEST = false`"
239239- );
240240- }
241241-242242- // Initialize metrics reporting.
243243- metrics::setup(config.metrics.as_ref()).context("failed to set up metrics exporter")?;
244244-245245- // Create a reqwest client that will be used for all outbound requests.
246246- let simple_client = reqwest::Client::builder()
247247- .user_agent(APP_USER_AGENT)
248248- .build()
249249- .context("failed to build requester client")?;
250250- let client = reqwest_middleware::ClientBuilder::new(simple_client.clone())
251251- .with(http_cache_reqwest::Cache(http_cache_reqwest::HttpCache {
252252- mode: CacheMode::Default,
253253- manager: MokaManager::default(),
254254- options: HttpCacheOptions::default(),
255255- }))
256256- .build();
257257-258258- tokio::fs::create_dir_all(&config.key.parent().context("should have parent")?)
259259- .await
260260- .context("failed to create key directory")?;
261261-262262- // Check if crypto keys exist. If not, create new ones.
263263- let (skey, rkey) = if let Ok(f) = std::fs::File::open(&config.key) {
264264- let keys: KeyData = serde_ipld_dagcbor::from_reader(std::io::BufReader::new(f))
265265- .context("failed to deserialize crypto keys")?;
266266-267267- let skey = Secp256k1Keypair::import(&keys.skey).context("failed to import signing key")?;
268268- let rkey = Secp256k1Keypair::import(&keys.rkey).context("failed to import rotation key")?;
269269-270270- (SigningKey(Arc::new(skey)), RotationKey(Arc::new(rkey)))
271271- } else {
272272- info!("signing keys not found, generating new ones");
273273-274274- let skey = Secp256k1Keypair::create(&mut rand::thread_rng());
275275- let rkey = Secp256k1Keypair::create(&mut rand::thread_rng());
276276-277277- let keys = KeyData {
278278- skey: skey.export(),
279279- rkey: rkey.export(),
280280- };
281281-282282- let mut f = std::fs::File::create(&config.key).context("failed to create key file")?;
283283- serde_ipld_dagcbor::to_writer(&mut f, &keys).context("failed to serialize crypto keys")?;
284284-285285- (SigningKey(Arc::new(skey)), RotationKey(Arc::new(rkey)))
286286- };
287287-288288- tokio::fs::create_dir_all(&config.repo.path).await?;
289289- tokio::fs::create_dir_all(&config.plc.path).await?;
290290- tokio::fs::create_dir_all(&config.blob.path).await?;
291291-292292- let cred = azure_identity::DefaultAzureCredential::new()
293293- .context("failed to create Azure credential")?;
294294-295295- // Create a database connection manager and pool for the main database.
296296- let pool =
297297- establish_pool(&config.db).context("failed to establish database connection pool")?;
298298- // Create a dictionary of database connection pools for each actor.
299299- let mut actor_pools = std::collections::HashMap::new();
300300- // let mut actor_blob_pools = std::collections::HashMap::new();
301301- // We'll determine actors by looking in the data/repo dir for .db files.
302302- let mut actor_dbs = tokio::fs::read_dir(&config.repo.path)
303303- .await
304304- .context("failed to read repo directory")?;
305305- while let Some(entry) = actor_dbs
306306- .next_entry()
307307- .await
308308- .context("failed to read repo dir")?
309309- {
310310- let path = entry.path();
311311- if path.extension().and_then(|s| s.to_str()) == Some("db") {
312312- let did = path
313313- .file_stem()
314314- .and_then(|s| s.to_str())
315315- .context("failed to get actor DID")?;
316316- let did = Did::from_str(did).expect("should be able to parse actor DID");
317317-318318- // Create a new database connection manager and pool for the actor.
319319- // The path for the SQLite connection needs to look like "sqlite://data/repo/<actor>.db"
320320- let path_repo = format!("sqlite://{}", path.display());
321321- let actor_repo_pool =
322322- establish_pool(&path_repo).context("failed to create database connection pool")?;
323323- // Create a new database connection manager and pool for the actor blobs.
324324- // The path for the SQLite connection needs to look like "sqlite://data/blob/<actor>.db"
325325- let path_blob = path_repo.replace("repo", "blob");
326326- let actor_blob_pool =
327327- establish_pool(&path_blob).context("failed to create database connection pool")?;
328328- drop(actor_pools.insert(
329329- did.to_string(),
330330- ActorPools {
331331- repo: actor_repo_pool,
332332- blob: actor_blob_pool,
333333- },
334334- ));
335335- }
336336- }
337337- // Apply pending migrations
338338- // let conn = pool.get().await?;
339339- // conn.run_pending_migrations(MIGRATIONS)
340340- // .expect("should be able to run migrations");
341341-342342- let (_fh, fhp) = firehose::spawn(client.clone(), config.clone());
343343-344344- let addr = config
345345- .listen_address
346346- .unwrap_or(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 8000));
347347-348348- let app = Router::new()
349349- .route("/", get(index))
350350- .merge(oauth::routes())
351351- .nest(
352352- "/xrpc",
353353- endpoints::routes()
354354- .merge(actor_endpoints::routes())
355355- .fallback(service_proxy),
356356- )
357357- // .layer(RateLimitLayer::new(30, Duration::from_secs(30)))
358358- .layer(CorsLayer::permissive())
359359- .layer(TraceLayer::new_for_http())
360360- .with_state(AppState {
361361- cred,
362362- config: config.clone(),
363363- db: pool.clone(),
364364- db_actors: actor_pools.clone(),
365365- client: client.clone(),
366366- simple_client,
367367- firehose: fhp,
368368- signing_key: skey,
369369- rotation_key: rkey,
370370- });
371371-372372- info!("listening on {addr}");
373373- info!("connect to: http://127.0.0.1:{}", addr.port());
374374-375375- // Determine whether or not this was the first startup (i.e. no accounts exist and no invite codes were created).
376376- // If so, create an invite code and share it via the console.
377377- let conn = pool.get().await.context("failed to get db connection")?;
378378-379379- #[derive(QueryableByName)]
380380- struct TotalCount {
381381- #[diesel(sql_type = diesel::sql_types::Integer)]
382382- total_count: i32,
383383- }
384384-385385- let result = conn.interact(move |conn| {
386386- diesel::sql_query(
387387- "SELECT (SELECT COUNT(*) FROM accounts) + (SELECT COUNT(*) FROM invites) AS total_count",
388388- )
389389- .get_result::<TotalCount>(conn)
390390- })
391391- .await
392392- .expect("should be able to query database")?;
393393-394394- let c = result.total_count;
395395-396396- #[expect(clippy::print_stdout)]
397397- if c == 0 {
398398- let uuid = Uuid::new_v4().to_string();
399399-400400- let uuid_clone = uuid.clone();
401401- _ = conn
402402- .interact(move |conn| {
403403- diesel::sql_query(
404404- "INSERT INTO invites (id, did, count, created_at) VALUES (?, NULL, 1, datetime('now'))",
405405- )
406406- .bind::<diesel::sql_types::Text, _>(uuid_clone)
407407- .execute(conn)
408408- .context("failed to create new invite code")
409409- .expect("should be able to create invite code")
410410- })
411411- .await
412412- .expect("should be able to create invite code");
413413-414414- // N.B: This is a sensitive message, so we're bypassing `tracing` here and
415415- // logging it directly to console.
416416- println!("=====================================");
417417- println!(" FIRST STARTUP ");
418418- println!("=====================================");
419419- println!("Use this code to create an account:");
420420- println!("{uuid}");
421421- println!("=====================================");
422422- }
423423-424424- let listener = TcpListener::bind(&addr)
425425- .await
426426- .context("failed to bind address")?;
427427-428428- // Serve the app, and request crawling from upstream relays.
429429- let serve = tokio::spawn(async move {
430430- axum::serve(listener, app.into_make_service())
431431- .await
432432- .context("failed to serve app")
433433- });
434434-435435- // Now that the app is live, request a crawl from upstream relays.
436436- firehose::reconnect_relays(&client, &config).await;
437437-438438- serve
439439- .await
440440- .map_err(Into::into)
441441- .and_then(|r| r)
442442- .context("failed to serve app")
443443-}
44454456#[tokio::main(flavor = "multi_thread")]
4467async fn main() -> anyhow::Result<()> {
447447- // Dispatch out to a separate function without a derive macro to help rust-analyzer along.
448448- run().await
449449-}
88+ // Parse command line arguments and call into the library's run function
99+ bluepds::run().await.context("failed to run application")
1010+}
+2-2
src/oauth.rs
···577577 .expect("Failed to query PAR request");
578578579579 // Authenticate the user
580580- use rsky_pds::schema::pds::account::dsl as AccountSchema;
581581- use rsky_pds::schema::pds::actor::dsl as ActorSchema;
580580+ use crate::schema::pds::account::dsl as AccountSchema;
581581+ use crate::schema::pds::actor::dsl as ActorSchema;
582582 let username_clone = username.to_owned();
583583 let account = db
584584 .get()