Skip to main content

autter_core/
config.rs

1use oiseau::config::{Configuration, DatabaseConfig};
2use pathbufd::PathBufD;
3use serde::{Deserialize, Serialize};
4
5#[derive(Clone, Debug, Serialize, Deserialize)]
6pub struct Config {
7    /// The name of the site. Shown in the UI.
8    #[serde(default = "default_name")]
9    pub name: String,
10    /// Database configuration.
11    #[serde(default = "default_database")]
12    pub database: DatabaseConfig,
13    /// Service hosts config.
14    #[serde(default)]
15    pub service_hosts: ServiceHostsConfig,
16    /// The public URL of this service.
17    #[serde(default)]
18    pub host: String,
19    /// Usernames which cannot be used by any user.
20    #[serde(default = "default_banned_usernames")]
21    pub banned_usernames: Vec<String>,
22    /// Security settings.
23    #[serde(default)]
24    pub security: SecurityConfig,
25    /// Directories config.
26    #[serde(default)]
27    pub dirs: DirectoriesConfig,
28    /// Stripe payments config.
29    pub stripe: StripeConfig,
30    #[serde(default)]
31    pub turnstile: TurnstileConfig,
32}
33
34fn default_banned_usernames() -> Vec<String> {
35    vec!["admin".to_string(), "settings".to_string()]
36}
37
38#[derive(Clone, Serialize, Deserialize, Debug)]
39pub struct TurnstileConfig {
40    pub site_key: String,
41    pub secret_key: String,
42}
43
44impl Default for TurnstileConfig {
45    fn default() -> Self {
46        Self {
47            site_key: "1x00000000000000000000AA".to_string(), // always passing, visible
48            secret_key: "1x0000000000000000000000000000000AA".to_string(), // always passing
49        }
50    }
51}
52
53#[derive(Clone, Debug, Serialize, Deserialize, Default)]
54pub struct SecurityConfig {
55    #[serde(default)]
56    pub accepting_purchases: bool,
57    #[serde(default)]
58    pub registration_enabled: bool,
59    /// Real IP header (for reverse proxy).
60    #[serde(default = "default_real_ip_header")]
61    pub real_ip_header: String,
62    /// The hostnames of approved login redirect destinations.
63    #[serde(default)]
64    pub approved_login_redirects: Vec<String>,
65}
66
67#[derive(Clone, Debug, Serialize, Deserialize)]
68pub struct ServiceHostsConfig {
69    #[serde(default = "default_shrimpcamp")]
70    pub shrimpcamp: String,
71    #[serde(default = "default_tetratto")]
72    pub tetratto: String,
73    #[serde(default = "default_buckets")]
74    pub buckets: String,
75    #[serde(default = "default_askall")]
76    pub askall: String,
77    #[serde(default = "default_issuestack")]
78    pub issuestack: String,
79    #[serde(default = "default_fluffle")]
80    pub fluffle: String,
81    #[serde(default = "default_overkit")]
82    pub overkit: String,
83    #[serde(default = "default_juicespace")]
84    pub juicespace: String,
85}
86
87fn default_shrimpcamp() -> String {
88    "https://about.shrimpcamp.com".to_string()
89}
90
91fn default_tetratto() -> String {
92    "https://tetratto.com".to_string()
93}
94
95fn default_askall() -> String {
96    "https://askall.cc".to_string()
97}
98
99fn default_issuestack() -> String {
100    "https://stack.shrimpcamp.com".to_string()
101}
102
103fn default_fluffle() -> String {
104    "https://fluffle.cc".to_string()
105}
106
107fn default_buckets() -> String {
108    "http://localhost:8020".to_string()
109}
110
111fn default_overkit() -> String {
112    "http://localhost:8026".to_string()
113}
114
115fn default_juicespace() -> String {
116    "https://juicespace.org".to_string()
117}
118
119impl Default for ServiceHostsConfig {
120    fn default() -> Self {
121        Self {
122            shrimpcamp: default_shrimpcamp(),
123            tetratto: default_tetratto(),
124            buckets: default_buckets(),
125            askall: default_askall(),
126            issuestack: default_issuestack(),
127            fluffle: default_fluffle(),
128            overkit: default_overkit(),
129            juicespace: default_juicespace(),
130        }
131    }
132}
133
134#[derive(Clone, Debug, Serialize, Deserialize)]
135pub struct DirectoriesConfig {
136    #[serde(default = "default_media")]
137    pub media: String,
138}
139
140fn default_media() -> String {
141    "media".to_string()
142}
143
144impl Default for DirectoriesConfig {
145    fn default() -> Self {
146        Self {
147            media: default_media(),
148        }
149    }
150}
151
152fn default_name() -> String {
153    "Autter".to_string()
154}
155
156fn default_real_ip_header() -> String {
157    "CF-Connecting-IP".to_string()
158}
159
160fn default_database() -> DatabaseConfig {
161    DatabaseConfig::default()
162}
163
164/// Configuration for Stripe integration.
165///
166/// User IDs are sent to Stripe through the payment link.
167/// <https://docs.stripe.com/payment-links/url-parameters#streamline-reconciliation-with-a-url-parameter>
168///
169/// # Testing
170///
171/// - Run `stripe login` using the Stripe CLI
172/// - Run `stripe listen --forward-to localhost:4118/api/v1/service_hooks/stripe`
173/// - Use testing card numbers: <https://docs.stripe.com/testing?testing-method=card-numbers#visa>
174#[derive(Clone, Serialize, Deserialize, Debug, Default)]
175pub struct StripeConfig {
176    /// Your Stripe API secret.
177    pub secret: String,
178    /// To apply benefits to user accounts, you should then go into the Stripe developer
179    /// "workbench" and create a new webhook. The webhook needs the scopes:
180    /// `invoice.payment_succeeded`, `customer.subscription.deleted`, `checkout.session.completed`, `charge.succeeded`.
181    ///
182    /// The webhook's destination address should be `{your server origin}/api/v1/service_hooks/stripe`.
183    ///
184    /// The signing secret can be found on the right after you have created the webhook.
185    pub webhook_signing_secret: String,
186    /// The URL of your customer billing portal.
187    ///
188    /// <https://docs.stripe.com/no-code/customer-portal>
189    pub billing_portal_url: String,
190    /// The text representation of prices. (like `$4 USD`)
191    pub price_texts: StripePriceTexts,
192    /// Product IDs from the Stripe dashboard.
193    ///
194    /// These are checked when we receive a webhook to ensure we provide the correct product.
195    pub product_ids: StripeProductIds,
196    /// The IDs of individual prices for products which require us to generate sessions ourselves.
197    pub price_ids: StripePriceIds,
198}
199
200#[derive(Clone, Serialize, Deserialize, Debug, Default)]
201pub struct StripePriceTexts {
202    pub organization: String,
203    pub user_reg: String,
204    pub seedling: String,
205    pub user_verification: String,
206}
207
208#[derive(Clone, Serialize, Deserialize, Debug, Default)]
209pub struct StripeProductIds {
210    pub organization: String,
211    pub user_reg: String,
212    pub seedling: String,
213    pub user_verification: String,
214}
215
216#[derive(Clone, Serialize, Deserialize, Debug, Default)]
217pub struct StripePriceIds {
218    pub organization: String,
219    pub user_reg: String,
220    pub seedling: String,
221    pub user_verification: String,
222}
223
224impl Configuration for Config {
225    fn db_config(&self) -> DatabaseConfig {
226        self.database.to_owned()
227    }
228}
229
230impl Default for Config {
231    fn default() -> Self {
232        Self {
233            name: default_name(),
234            database: default_database(),
235            service_hosts: ServiceHostsConfig::default(),
236            host: String::new(),
237            banned_usernames: default_banned_usernames(),
238            security: SecurityConfig::default(),
239            dirs: DirectoriesConfig::default(),
240            stripe: StripeConfig::default(),
241            turnstile: TurnstileConfig::default(),
242        }
243    }
244}
245
246impl Config {
247    /// Read the configuration file.
248    pub fn read() -> Self {
249        toml::from_str(
250            &match std::fs::read_to_string(PathBufD::current().join("app.toml")) {
251                Ok(x) => x,
252                Err(_) => {
253                    let x = Config::default();
254
255                    std::fs::write(
256                        PathBufD::current().join("app.toml"),
257                        toml::to_string_pretty(&x).expect("failed to serialize config"),
258                    )
259                    .expect("failed to write config");
260
261                    return x;
262                }
263            },
264        )
265        .expect("failed to deserialize config")
266    }
267}