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 if url.starts_with("postgres://") || url.starts_with("postgresql://") {
186 "postgres".into()
187 } else {
188 "postgres".into()
189 }
190 });
191 let driver = match driver_str.as_str() {
192 "postgres" | "pgsql" | "pg" => ConnectionDriver::Postgres,
193 other => ConnectionDriver::Other(other.to_string()),
194 };
195
196 Self {
197 driver,
198 url,
199 read_urls,
200 pool_size,
201 }
202 }
203}
204
205#[derive(Debug, Clone)]
206pub struct SessionConfig {
207 pub driver: String,
208 pub lifetime_minutes: i64,
209 pub cookie_name: String,
210 pub same_site: String,
211 pub secure: bool,
212}
213
214impl SessionConfig {
215 pub fn from_env() -> Self {
216 Self {
217 driver: env::var("SESSION_DRIVER").unwrap_or_else(|_| "file".to_string()),
218 lifetime_minutes: env::var("SESSION_LIFETIME")
219 .ok()
220 .and_then(|v| v.parse().ok())
221 .unwrap_or(120),
222 cookie_name: env::var("SESSION_COOKIE").unwrap_or_else(|_| "anvil_session".to_string()),
223 same_site: env::var("SESSION_SAME_SITE").unwrap_or_else(|_| "lax".to_string()),
224 secure: env::var("SESSION_SECURE")
225 .ok()
226 .and_then(|v| v.parse().ok())
227 .unwrap_or(false),
228 }
229 }
230}
231
232#[derive(Debug, Clone)]
233pub struct CacheConfig {
234 pub driver: String,
235 pub ttl_seconds: u64,
236}
237
238impl CacheConfig {
239 pub fn from_env() -> Self {
240 Self {
241 driver: env::var("CACHE_DRIVER").unwrap_or_else(|_| "moka".to_string()),
242 ttl_seconds: env::var("CACHE_TTL")
243 .ok()
244 .and_then(|v| v.parse().ok())
245 .unwrap_or(3600),
246 }
247 }
248}
249
250#[derive(Debug, Clone)]
251pub struct QueueConfig {
252 pub driver: String,
253 pub default_queue: String,
254}
255
256impl QueueConfig {
257 pub fn from_env() -> Self {
258 Self {
259 driver: env::var("QUEUE_DRIVER").unwrap_or_else(|_| "database".to_string()),
260 default_queue: env::var("QUEUE_DEFAULT").unwrap_or_else(|_| "default".to_string()),
261 }
262 }
263}
264
265#[derive(Debug, Clone)]
266pub struct MailConfig {
267 pub mailer: String,
268 pub host: String,
269 pub port: u16,
270 pub username: String,
271 pub password: String,
272 pub from_address: String,
273 pub from_name: String,
274}
275
276impl MailConfig {
277 pub fn from_env() -> Self {
278 Self {
279 mailer: env::var("MAIL_MAILER").unwrap_or_else(|_| "smtp".to_string()),
280 host: env::var("MAIL_HOST").unwrap_or_else(|_| "localhost".to_string()),
281 port: env::var("MAIL_PORT")
282 .ok()
283 .and_then(|v| v.parse().ok())
284 .unwrap_or(1025),
285 username: env::var("MAIL_USERNAME").unwrap_or_default(),
286 password: env::var("MAIL_PASSWORD").unwrap_or_default(),
287 from_address: env::var("MAIL_FROM_ADDRESS")
288 .unwrap_or_else(|_| "hello@example.com".to_string()),
289 from_name: env::var("MAIL_FROM_NAME").unwrap_or_else(|_| "Anvil".to_string()),
290 }
291 }
292}
293
294#[derive(Debug, Clone)]
295pub struct FilesystemConfig {
296 pub default_disk: String,
297 pub local_root: String,
298}
299
300impl FilesystemConfig {
301 pub fn from_env() -> Self {
302 Self {
303 default_disk: env::var("FILESYSTEM_DISK").unwrap_or_else(|_| "local".to_string()),
304 local_root: env::var("FILESYSTEM_LOCAL_ROOT")
305 .unwrap_or_else(|_| "storage/app".to_string()),
306 }
307 }
308}