···3737use clap::Parser;
3838use clap_verbosity_flag::{InfoLevel, Verbosity, log::LevelFilter};
3939use config::AppConfig;
4040+use db::establish_pool;
4141+use deadpool_diesel::sqlite::Pool;
4042use diesel::prelude::*;
4141-use diesel::r2d2::{self, ConnectionManager};
4242-use diesel_migrations::{EmbeddedMigrations, MigrationHarness, embed_migrations};
4343+use diesel_migrations::{EmbeddedMigrations, embed_migrations};
4344#[expect(clippy::pub_use, clippy::useless_attribute)]
4445pub use error::Error;
4546use figment::{Figment, providers::Format as _};
···6869pub type Result<T> = std::result::Result<T, Error>;
6970/// The reqwest client type with middleware.
7071pub type Client = reqwest_middleware::ClientWithMiddleware;
7171-/// The database connection pool.
7272-pub type Db = r2d2::Pool<ConnectionManager<SqliteConnection>>;
7372/// The Azure credential type.
7473pub type Cred = Arc<dyn TokenCredential>;
7574···132131 verbosity: Verbosity<InfoLevel>,
133132}
134133134134+struct ActorPools {
135135+ repo: Pool,
136136+ blob: Pool,
137137+}
138138+impl Clone for ActorPools {
139139+ fn clone(&self) -> Self {
140140+ Self {
141141+ repo: self.repo.clone(),
142142+ blob: self.blob.clone(),
143143+ }
144144+ }
145145+}
146146+135147#[expect(clippy::arbitrary_source_item_ordering, reason = "arbitrary")]
136148#[derive(Clone, FromRef)]
137149struct AppState {
···139151 config: AppConfig,
140152 /// The Azure credential.
141153 cred: Cred,
142142- /// The database connection pool.
143143- db: Db,
154154+ /// The main database connection pool. Used for common PDS data, like invite codes.
155155+ db: Pool,
156156+ /// Actor-specific database connection pools. Hashed by DID.
157157+ db_actors: std::collections::HashMap<String, ActorPools>,
144158145159 /// The HTTP client with middleware.
146160 client: Client,
···291305#[expect(
292306 clippy::cognitive_complexity,
293307 clippy::too_many_lines,
308308+ unused_qualifications,
294309 reason = "main function has high complexity"
295310)]
296311async fn run() -> anyhow::Result<()> {
···388403 let cred = azure_identity::DefaultAzureCredential::new()
389404 .context("failed to create Azure credential")?;
390405391391- // Create a database connection manager and pool
392392- let manager = ConnectionManager::<SqliteConnection>::new(&config.db);
393393- let db = r2d2::Pool::builder()
394394- .build(manager)
395395- .context("failed to create database connection pool")?;
406406+ // Create a database connection manager and pool for the main database.
407407+ let pool =
408408+ establish_pool(&config.db).context("failed to establish database connection pool")?;
409409+ // Create a dictionary of database connection pools for each actor.
410410+ let mut actor_pools = std::collections::HashMap::new();
411411+ // let mut actor_blob_pools = std::collections::HashMap::new();
412412+ // We'll determine actors by looking in the data/repo dir for .db files.
413413+ let mut actor_dbs = tokio::fs::read_dir(&config.repo.path)
414414+ .await
415415+ .context("failed to read repo directory")?;
416416+ while let Some(entry) = actor_dbs
417417+ .next_entry()
418418+ .await
419419+ .context("failed to read repo dir")?
420420+ {
421421+ let path = entry.path();
422422+ if path.extension().and_then(|s| s.to_str()) == Some("db") {
423423+ let did = path
424424+ .file_stem()
425425+ .and_then(|s| s.to_str())
426426+ .context("failed to get actor DID")?;
427427+ let did = Did::from_str(did).expect("should be able to parse actor DID");
396428429429+ // Create a new database connection manager and pool for the actor.
430430+ // The path for the SQLite connection needs to look like "sqlite://data/repo/<actor>.db"
431431+ let path_repo = format!("sqlite://{}", path.display());
432432+ let actor_repo_pool =
433433+ establish_pool(&path_repo).context("failed to create database connection pool")?;
434434+ // Create a new database connection manager and pool for the actor blobs.
435435+ // The path for the SQLite connection needs to look like "sqlite://data/blob/<actor>.db"
436436+ let path_blob = path_repo.replace("repo", "blob");
437437+ let actor_blob_pool =
438438+ establish_pool(&path_blob).context("failed to create database connection pool")?;
439439+ actor_pools.insert(
440440+ did.to_string(),
441441+ ActorPools {
442442+ repo: actor_repo_pool,
443443+ blob: actor_blob_pool,
444444+ },
445445+ );
446446+ }
447447+ }
397448 // Apply pending migrations
398398- let conn = &mut db
399399- .get()
400400- .context("failed to get database connection for migrations")?;
401401- conn.run_pending_migrations(MIGRATIONS)
402402- .expect("should be able to run migrations");
449449+ // let conn = pool.get().await?;
450450+ // conn.run_pending_migrations(MIGRATIONS)
451451+ // .expect("should be able to run migrations");
403452404453 let (_fh, fhp) = firehose::spawn(client.clone(), config.clone());
405454···422471 .with_state(AppState {
423472 cred,
424473 config: config.clone(),
425425- db: db.clone(),
474474+ db: pool.clone(),
475475+ db_actors: actor_pools.clone(),
426476 client: client.clone(),
427477 simple_client,
428478 firehose: fhp,
···435485436486 // Determine whether or not this was the first startup (i.e. no accounts exist and no invite codes were created).
437487 // If so, create an invite code and share it via the console.
438438- let conn = &mut db.get().context("failed to get database connection")?;
488488+ let conn = pool.get().await.context("failed to get db connection")?;
439489440490 #[derive(QueryableByName)]
441491 struct TotalCount {
···443493 total_count: i32,
444494 }
445495446446- let result = diesel::sql_query(
447447- "SELECT (SELECT COUNT(*) FROM accounts) + (SELECT COUNT(*) FROM invites) AS total_count",
448448- )
449449- .get_result::<TotalCount>(conn)
450450- .context("failed to query database")?;
496496+ // let result = diesel::sql_query(
497497+ // "SELECT (SELECT COUNT(*) FROM accounts) + (SELECT COUNT(*) FROM invites) AS total_count",
498498+ // )
499499+ // .get_result::<TotalCount>(conn)
500500+ // .context("failed to query database")?;
501501+ let result = conn.interact(move |conn| {
502502+ diesel::sql_query(
503503+ "SELECT (SELECT COUNT(*) FROM accounts) + (SELECT COUNT(*) FROM invites) AS total_count",
504504+ )
505505+ .get_result::<TotalCount>(conn)
506506+ })
507507+ .await
508508+ .expect("should be able to query database")?;
451509452510 let c = result.total_count;
453511···455513 if c == 0 {
456514 let uuid = Uuid::new_v4().to_string();
457515458458- diesel::sql_query(
516516+ let uuid_clone = uuid.clone();
517517+ conn.interact(move |conn| {
518518+ diesel::sql_query(
459519 "INSERT INTO invites (id, did, count, created_at) VALUES (?, NULL, 1, datetime('now'))",
460520 )
461461- .bind::<diesel::sql_types::Text, _>(uuid.clone())
521521+ .bind::<diesel::sql_types::Text, _>(uuid_clone)
462522 .execute(conn)
463463- .context("failed to create new invite code")?;
523523+ .context("failed to create new invite code")
524524+ .expect("should be able to create invite code")
525525+ });
464526465527 // N.B: This is a sensitive message, so we're bypassing `tracing` here and
466528 // logging it directly to console.
+1-1
src/tests.rs
···222222 let opts = SqliteConnectOptions::from_str(&config.db)
223223 .context("failed to parse database options")?
224224 .create_if_missing(true);
225225- let db = SqlitePool::connect_with(opts).await?;
225225+ let db = SqliteDbConn::connect_with(opts).await?;
226226227227 sqlx::migrate!()
228228 .run(&db)