···11+# required
22+USER_DID=did:plc:4zht3z4caxwrw3dlsybodywc # did
33+DATABASE_URL=postgres://admin@localhost:5432/meview # postgres uri
44+55+# optional
66+USER_PDS_URL=katproto.girlonthemoon.xyz # if ommited, will resolve from did
77+USER_EXPORT_URL=katproto.girlonthemoon.xyz # if ommited, will copy USER_PDS_URL
88+USER_SUBSCRIBE_URL=katproto.girlonthemoon.xyz # if ommited, will copy USER_PDS_URL
+5-5
src/backfill/mod.rs
···66//! 4. convert cbor data to json
77//! 5. store in db (limit to DB_MAX_REQ / 4 to avoid err)
8899-use std::str::FromStr;
1010-119use ipld_core::cid::multibase::Base;
1210use jacquard::url::Url;
1311use sqlx::{Pool, Postgres, query};
···3634}
37353836pub async fn backfill(
3939- pds: &str,
4037 conn: &Pool<Postgres>,
4138 time: Option<std::time::Instant>,
4239) -> Result<(), Error> {
4343- let pds = Url::from_str(&format!("https://{pds}/")).unwrap();
4444- let car = load_car(config::USER.clone(), pds).await?;
4040+ let car = load_car(
4141+ config::USER_DID.clone(),
4242+ Url::parse(&format!("https://{}/", config::USER_EXPORT_URL)).unwrap(),
4343+ )
4444+ .await?;
45454646 if let Some(time) = time {
4747 println!("Downloaded car file ({:?})", time.elapsed());
-62
src/config.rs
···11-//! get static and parsed environment variables
22-//!
33-//! USER is from env variable USER and parsed into a jacquard Did
44-//! POSTGRES_URL is from POSTGRES_USER, POSTGRES_PASSWORD, and POSTGRES_HOST
55-66-use jacquard::types::string::Did;
77-use std::env;
88-use std::sync::LazyLock;
99-1010-pub const DB_MAX_REQ: usize = 65535;
1111-1212-// this should be loaded before the program starts any threads
1313-// if this panics threads that access it will be poisoned
1414-pub static USER: LazyLock<Did<'static>> = LazyLock::new(|| {
1515- let Ok(env) = env::var("INDEX_USER") else {
1616- panic!("INDEX_USER not set");
1717- };
1818-1919- let Ok(did) = Did::new_owned(env) else {
2020- panic!("INDEX_USER was not a valid did")
2121- };
2222-2323- did
2424-});
2525-2626-pub static POSTGRES_URL: LazyLock<String> = LazyLock::new(|| {
2727- if let Ok(url) = env::var("DATABASE_URL") {
2828- return url;
2929- }
3030-3131- let user = env::var("POSTGRES_USER");
3232- let db = env::var("POSTGRES_DATABASE").or_else(|_| user.clone());
3333- let password = env::var("POSTGRES_PASSWORD");
3434- let host = env::var("POSTGRES_HOST");
3535-3636- if let Ok(user) = user.clone()
3737- && let Ok(db) = db.clone()
3838- && let Ok(password) = password.clone()
3939- && let Ok(host) = host.clone()
4040- {
4141- format!("postgres://{}:{}@{}/{}", user, password, host, db)
4242- } else {
4343- let missing = [
4444- (user, "USER"),
4545- (db, "DATABASE"),
4646- (password, "PASSWORD"),
4747- (host, "HOST"),
4848- ]
4949- .iter()
5050- .filter_map(|x| {
5151- if x.0.is_err() {
5252- Some(String::from("POSTGRES_") + x.1)
5353- } else {
5454- None
5555- }
5656- })
5757- .collect::<Vec<String>>()
5858- .join(", ");
5959-6060- panic!("Could not generate database url. Missing environment variables {}. Set DATABASE_URL to define the postgres url manually", missing);
6161- }
6262-});
+96
src/config/config_value.rs
···11+//! Define `ConfigValue` & helpers
22+33+use std::fmt::Display;
44+use std::ops::Deref;
55+use std::pin::Pin;
66+use std::sync::OnceLock;
77+88+/// A struct which acts like a `OnceLock` but with async support
99+pub struct ConfigValue<T> {
1010+ inner: Inner<T>,
1111+}
1212+1313+enum Inner<T> {
1414+ Sync {
1515+ lock: OnceLock<T>,
1616+ func: fn() -> T,
1717+ },
1818+ Async {
1919+ lock: OnceLock<T>,
2020+ func: fn() -> Pin<Box<dyn Future<Output = T>>>,
2121+ },
2222+}
2323+2424+impl<T> ConfigValue<T> {
2525+ /// create a new synchronous ConfigValue
2626+ ///
2727+ /// this is the same as a OnceLock basically
2828+ pub const fn new_sync(func: fn() -> T) -> ConfigValue<T> {
2929+ ConfigValue {
3030+ inner: Inner::Sync {
3131+ lock: OnceLock::new(),
3232+ func,
3333+ },
3434+ }
3535+ }
3636+3737+ /// create a new async based ConfigValue
3838+ pub const fn new_async(func: fn() -> Pin<Box<dyn Future<Output = T>>>) -> ConfigValue<T> {
3939+ ConfigValue {
4040+ inner: Inner::Async {
4141+ lock: OnceLock::new(),
4242+ func,
4343+ },
4444+ }
4545+ }
4646+4747+ /// get the value. if the value is uninitialized, initialize it
4848+ pub async fn get(&self) -> &T {
4949+ match &self.inner {
5050+ Inner::Sync { lock, func } => lock.get_or_init(func),
5151+ Inner::Async { lock, func } => {
5252+ if let Some(val) = lock.get() {
5353+ return val;
5454+ }
5555+ let res = func().await;
5656+ let _ = lock.set(res);
5757+ lock.wait()
5858+ }
5959+ }
6060+ }
6161+6262+ /// same as `get` but discards the result
6363+ /// this should be called in main to make sure that values are properly initialized before deref
6464+ pub async fn init(&self) {
6565+ let _ = self.get().await;
6666+ }
6767+}
6868+6969+impl<T> Deref for ConfigValue<T> {
7070+ type Target = T;
7171+ fn deref(&self) -> &Self::Target {
7272+ match &self.inner {
7373+ Inner::Sync { lock, func: _ } => lock.wait(),
7474+ Inner::Async { lock, func: _ } => lock.wait(),
7575+ }
7676+ }
7777+}
7878+7979+impl<T> Display for ConfigValue<T>
8080+where
8181+ T: Display,
8282+{
8383+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
8484+ self.deref().fmt(f)
8585+ }
8686+}
8787+8888+#[macro_export]
8989+/// initializes all values passed in. useful to batch init without tons of .init values
9090+macro_rules! init {
9191+ ( $( $x:expr ),* ) => {
9292+ $(
9393+ $x.init().await;
9494+ )*
9595+ };
9696+}
+95
src/config/mod.rs
···11+//! get static and parsed environment variables
22+//!
33+//! USER_DID is parsed into a jacquard Did
44+//! DATABASE_URL is from DATABASE_URL or POSTGRES_USER, POSTGRES_PASSWORD, POSTGRES_HOST, and POSTGRES_DATABASE
55+//! USER_PDS_URL is resolved from USER_DID if ommited
66+//! USER_EXPORT_URL falls back to USER_PDS_URL
77+//! USER_SUBSCRIBE_URL falls back to USER_PDS_URL
88+99+use jacquard::prelude::IdentityResolver;
1010+use jacquard::types::string::Did;
1111+use std::env;
1212+1313+pub const DB_MAX_REQ: usize = 65535;
1414+1515+mod config_value;
1616+1717+use crate::config::config_value::ConfigValue;
1818+1919+// this should be loaded before the program starts any threads
2020+// if this panics threads that access it will be poisoned
2121+pub static USER_DID: ConfigValue<Did<'static>> = ConfigValue::new_sync(|| {
2222+ let Ok(env) = env::var("USER_DID") else {
2323+ panic!("USER_DID not set");
2424+ };
2525+2626+ let Ok(did) = Did::new_owned(env) else {
2727+ panic!("USER_DID was not a valid did")
2828+ };
2929+3030+ did
3131+});
3232+3333+pub static DATABASE_URL: ConfigValue<String> = ConfigValue::new_sync(|| {
3434+ if let Ok(url) = env::var("DATABASE_URL") {
3535+ return url;
3636+ }
3737+3838+ let user = env::var("POSTGRES_USER");
3939+ let db = env::var("POSTGRES_DATABASE").or_else(|_| user.clone());
4040+ let password = env::var("POSTGRES_PASSWORD");
4141+ let host = env::var("POSTGRES_HOST");
4242+4343+ if let Ok(user) = user.clone()
4444+ && let Ok(db) = db.clone()
4545+ && let Ok(password) = password.clone()
4646+ && let Ok(host) = host.clone()
4747+ {
4848+ format!("postgres://{}:{}@{}/{}", user, password, host, db)
4949+ } else {
5050+ let missing = [
5151+ (user, "USER"),
5252+ (db, "DATABASE"),
5353+ (password, "PASSWORD"),
5454+ (host, "HOST"),
5555+ ]
5656+ .iter()
5757+ .filter_map(|x| {
5858+ if x.0.is_err() {
5959+ Some(String::from("POSTGRES_") + x.1)
6060+ } else {
6161+ None
6262+ }
6363+ })
6464+ .collect::<Vec<String>>()
6565+ .join(", ");
6666+6767+ panic!(
6868+ "Could not generate database url. Missing environment variables {}. Set DATABASE_URL to define the postgres url manually",
6969+ missing
7070+ );
7171+ }
7272+});
7373+7474+pub static USER_PDS_URL: ConfigValue<String> = ConfigValue::new_async(|| {
7575+ Box::pin(async {
7676+ if let Ok(url) = env::var("USER_PDS_URL") {
7777+ url
7878+ } else {
7979+ let resolver = jacquard::identity::PublicResolver::default();
8080+ resolver
8181+ .pds_for_did(&self::USER_DID)
8282+ .await
8383+ .unwrap()
8484+ .domain()
8585+ .unwrap()
8686+ .to_string()
8787+ }
8888+ })
8989+});
9090+9191+pub static USER_EXPORT_URL: ConfigValue<String> =
9292+ ConfigValue::new_sync(|| env::var("USER_EXPORT_URL").unwrap_or(USER_PDS_URL.clone()));
9393+9494+pub static USER_SUBSCRIBE_URL: ConfigValue<String> =
9595+ ConfigValue::new_sync(|| env::var("USER_SUBSCRIBE_URL").unwrap_or(USER_PDS_URL.clone()));
+1-1
src/db.rs
···44use sqlx::{Pool, Postgres, migrate, postgres::PgPool};
5566pub async fn conn() -> Pool<Postgres> {
77- let conn = match PgPool::connect(&config::POSTGRES_URL).await {
77+ let conn = match PgPool::connect(&config::DATABASE_URL).await {
88 Ok(val) => val,
99 Err(err) => {
1010 panic!("Could not connect to the database. Got error {err}");