1use figment::providers::{Env, Format as _, Toml};
2use figment::Figment;
3use serde::Deserialize;
4
5pub fn load_config() -> eyre::Result<Config> {
6 let conf = Figment::new()
7 .merge(Toml::file("Config.toml"))
8 .merge(Env::prefixed("PKC_").split("__"))
9 .extract()?;
10
11 Ok(conf)
12}
13
14#[derive(Debug, Deserialize)]
15pub struct Config {
16 pub index_uri: String,
17 pub database: deadpool_postgres::Config,
18 pub plc_directory: Option<String>,
19 /// Adds contact details (email / bluesky handle / website) to the UA header.
20 pub ua_contact: Option<String>,
21
22 /// Time-based retention window in days
23 ///
24 /// When set, only process and store records from the last N days.
25 /// Records older than this (based on TID timestamps) will be filtered out.
26 ///
27 /// This applies to:
28 /// - Posts (app.bsky.feed.post)
29 /// - Likes (app.bsky.feed.like)
30 /// - Reposts (app.bsky.feed.repost)
31 /// - References to old posts (reply parents, quote embeds, like/repost subjects)
32 ///
33 /// Other record types (Profiles, Lists, Follows, Blocks) are always processed.
34 ///
35 /// Default: None (no retention limit, process all records)
36 pub retention_days: Option<u32>,
37
38 /// Configuration items specific to indexer
39 pub indexer: Option<IndexerConfig>,
40}
41
42#[derive(Debug, Deserialize, Clone)]
43pub struct IndexerConfig {
44 /// Tap WebSocket URL (e.g., "ws://localhost:2480/channel")
45 pub tap_websocket_url: String,
46 /// Tap admin API URL (e.g., "http://localhost:2480")
47 pub tap_admin_url: String,
48 /// Tap admin password (if authentication is required)
49 pub tap_admin_password: Option<String>,
50 /// Number of worker threads for processing events
51 #[serde(default = "default_indexer_workers")]
52 pub workers: u8,
53 /// Maximum number of unacknowledged events to buffer
54 #[serde(default = "default_max_pending_acks")]
55 pub max_pending_acks: usize,
56}
57
58
59const fn default_indexer_workers() -> u8 {
60 4
61}
62
63const fn default_max_pending_acks() -> usize {
64 1000
65}
66