1use std::env;
4
5pub fn load_dotenv() {
6 let _ = dotenvy::dotenv();
7}
8
9#[derive(Debug, Clone)]
10pub struct AppConfig {
11 pub name: String,
12 pub env: String,
13 pub key: String,
14 pub debug: bool,
15 pub url: String,
16}
17
18impl AppConfig {
19 pub fn from_env() -> Self {
20 Self {
21 name: env::var("APP_NAME").unwrap_or_else(|_| "Anvil".to_string()),
22 env: env::var("APP_ENV").unwrap_or_else(|_| "production".to_string()),
23 key: env::var("APP_KEY").unwrap_or_default(),
24 debug: env::var("APP_DEBUG")
25 .ok()
26 .and_then(|v| v.parse().ok())
27 .unwrap_or(false),
28 url: env::var("APP_URL").unwrap_or_else(|_| "http://localhost:8080".to_string()),
29 }
30 }
31
32 pub fn is_local(&self) -> bool {
33 self.env == "local" || self.env == "development"
34 }
35}
36
37#[derive(Debug, Clone)]
54pub struct DatabaseConfig {
55 pub default: String,
56 pub connections: indexmap::IndexMap<String, ConnectionConfig>,
57}
58
59#[derive(Debug, Clone)]
61pub struct ConnectionConfig {
62 pub driver: ConnectionDriver,
63 pub url: String,
65 pub read_urls: Vec<String>,
67 pub pool_size: u32,
68}
69
70#[derive(Debug, Clone, PartialEq, Eq)]
71pub enum ConnectionDriver {
72 Postgres,
73 Other(String),
75}
76
77impl DatabaseConfig {
78 pub fn from_env() -> Self {
79 let names = env::var("DB_CONNECTIONS")
84 .map(|s| {
85 s.split(',')
86 .map(|t| t.trim().to_string())
87 .filter(|t| !t.is_empty())
88 .collect::<Vec<_>>()
89 })
90 .unwrap_or_else(|_| vec!["default".to_string()]);
91
92 let default = env::var("DB_DEFAULT").unwrap_or_else(|_| {
93 names
94 .first()
95 .cloned()
96 .unwrap_or_else(|| "default".to_string())
97 });
98
99 let mut connections = indexmap::IndexMap::new();
100 for name in &names {
101 let cfg = ConnectionConfig::from_env(name);
102 connections.insert(name.clone(), cfg);
103 }
104
105 Self {
106 default,
107 connections,
108 }
109 }
110
111 pub fn default_url(&self) -> &str {
113 self.connections
114 .get(&self.default)
115 .map(|c| c.url.as_str())
116 .unwrap_or("")
117 }
118
119 pub fn default_pool_size(&self) -> u32 {
121 self.connections
122 .get(&self.default)
123 .map(|c| c.pool_size)
124 .unwrap_or(10)
125 }
126
127 pub fn single(url: impl Into<String>, pool_size: u32) -> Self {
129 let mut connections = indexmap::IndexMap::new();
130 connections.insert(
131 "default".to_string(),
132 ConnectionConfig {
133 driver: ConnectionDriver::Postgres,
134 url: url.into(),
135 read_urls: Vec::new(),
136 pool_size,
137 },
138 );
139 Self {
140 default: "default".to_string(),
141 connections,
142 }
143 }
144}
145
146impl ConnectionConfig {
147 pub fn from_env(name: &str) -> Self {
148 let prefix = if name == "default" {
149 String::new()
150 } else {
151 format!("DB_{}_", name.to_ascii_uppercase())
152 };
153 let url = if name == "default" {
154 env::var("DATABASE_URL")
155 .or_else(|_| env::var(format!("{prefix}URL")))
156 .unwrap_or_else(|_| "postgres://postgres:postgres@localhost:5432/anvil".to_string())
157 } else {
158 env::var(format!("{prefix}URL")).unwrap_or_default()
159 };
160
161 let pool_size = if name == "default" {
162 env::var("DB_POOL")
163 .or_else(|_| env::var(format!("{prefix}POOL")))
164 .ok()
165 .and_then(|v| v.parse().ok())
166 .unwrap_or(10)
167 } else {
168 env::var(format!("{prefix}POOL"))
169 .ok()
170 .and_then(|v| v.parse().ok())
171 .unwrap_or(10)
172 };
173
174 let read_urls = env::var(format!("{prefix}READ_URLS"))
175 .map(|s| {
176 s.split(',')
177 .map(|t| t.trim().to_string())
178 .filter(|t| !t.is_empty())
179 .collect()
180 })
181 .unwrap_or_default();
182
183 let driver_str = env::var(format!("{prefix}DRIVER")).unwrap_or_else(|_| {
184 let _ = url.starts_with("postgres://") || url.starts_with("postgresql://");
187 "postgres".into()
188 });
189 let driver = match driver_str.as_str() {
190 "postgres" | "pgsql" | "pg" => ConnectionDriver::Postgres,
191 other => ConnectionDriver::Other(other.to_string()),
192 };
193
194 Self {
195 driver,
196 url,
197 read_urls,
198 pool_size,
199 }
200 }
201}
202
203#[derive(Debug, Clone)]
204pub struct SessionConfig {
205 pub driver: String,
206 pub lifetime_minutes: i64,
207 pub cookie_name: String,
208 pub same_site: String,
209 pub secure: bool,
210}
211
212impl SessionConfig {
213 pub fn from_env() -> Self {
214 Self {
215 driver: env::var("SESSION_DRIVER").unwrap_or_else(|_| "file".to_string()),
216 lifetime_minutes: env::var("SESSION_LIFETIME")
217 .ok()
218 .and_then(|v| v.parse().ok())
219 .unwrap_or(120),
220 cookie_name: env::var("SESSION_COOKIE").unwrap_or_else(|_| "anvil_session".to_string()),
221 same_site: env::var("SESSION_SAME_SITE").unwrap_or_else(|_| "lax".to_string()),
222 secure: env::var("SESSION_SECURE")
223 .ok()
224 .and_then(|v| v.parse().ok())
225 .unwrap_or(false),
226 }
227 }
228}
229
230#[derive(Debug, Clone)]
231pub struct CacheConfig {
232 pub driver: String,
233 pub ttl_seconds: u64,
234}
235
236impl CacheConfig {
237 pub fn from_env() -> Self {
238 Self {
239 driver: env::var("CACHE_DRIVER").unwrap_or_else(|_| "moka".to_string()),
240 ttl_seconds: env::var("CACHE_TTL")
241 .ok()
242 .and_then(|v| v.parse().ok())
243 .unwrap_or(3600),
244 }
245 }
246}
247
248#[derive(Debug, Clone)]
249pub struct QueueConfig {
250 pub driver: String,
251 pub default_queue: String,
252}
253
254impl QueueConfig {
255 pub fn from_env() -> Self {
256 Self {
257 driver: env::var("QUEUE_DRIVER").unwrap_or_else(|_| "database".to_string()),
258 default_queue: env::var("QUEUE_DEFAULT").unwrap_or_else(|_| "default".to_string()),
259 }
260 }
261}
262
263#[derive(Debug, Clone)]
264pub struct MailConfig {
265 pub mailer: String,
266 pub host: String,
267 pub port: u16,
268 pub username: String,
269 pub password: String,
270 pub from_address: String,
271 pub from_name: String,
272}
273
274impl MailConfig {
275 pub fn from_env() -> Self {
276 Self {
277 mailer: env::var("MAIL_MAILER").unwrap_or_else(|_| "smtp".to_string()),
278 host: env::var("MAIL_HOST").unwrap_or_else(|_| "localhost".to_string()),
279 port: env::var("MAIL_PORT")
280 .ok()
281 .and_then(|v| v.parse().ok())
282 .unwrap_or(1025),
283 username: env::var("MAIL_USERNAME").unwrap_or_default(),
284 password: env::var("MAIL_PASSWORD").unwrap_or_default(),
285 from_address: env::var("MAIL_FROM_ADDRESS")
286 .unwrap_or_else(|_| "hello@example.com".to_string()),
287 from_name: env::var("MAIL_FROM_NAME").unwrap_or_else(|_| "Anvil".to_string()),
288 }
289 }
290}
291
292#[derive(Debug, Clone)]
293pub struct FilesystemConfig {
294 pub default_disk: String,
295 pub local_root: String,
296}
297
298impl FilesystemConfig {
299 pub fn from_env() -> Self {
300 Self {
301 default_disk: env::var("FILESYSTEM_DISK").unwrap_or_else(|_| "local".to_string()),
302 local_root: env::var("FILESYSTEM_LOCAL_ROOT")
303 .unwrap_or_else(|_| "storage/app".to_string()),
304 }
305 }
306}