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.first().cloned().unwrap_or_else(|| "default".to_string())
94 });
95
96 let mut connections = indexmap::IndexMap::new();
97 for name in &names {
98 let cfg = ConnectionConfig::from_env(name);
99 connections.insert(name.clone(), cfg);
100 }
101
102 Self { default, connections }
103 }
104
105 pub fn default_url(&self) -> &str {
107 self.connections
108 .get(&self.default)
109 .map(|c| c.url.as_str())
110 .unwrap_or("")
111 }
112
113 pub fn default_pool_size(&self) -> u32 {
115 self.connections
116 .get(&self.default)
117 .map(|c| c.pool_size)
118 .unwrap_or(10)
119 }
120
121 pub fn single(url: impl Into<String>, pool_size: u32) -> Self {
123 let mut connections = indexmap::IndexMap::new();
124 connections.insert(
125 "default".to_string(),
126 ConnectionConfig {
127 driver: ConnectionDriver::Postgres,
128 url: url.into(),
129 read_urls: Vec::new(),
130 pool_size,
131 },
132 );
133 Self {
134 default: "default".to_string(),
135 connections,
136 }
137 }
138}
139
140impl ConnectionConfig {
141 pub fn from_env(name: &str) -> Self {
142 let prefix = if name == "default" {
143 String::new()
144 } else {
145 format!("DB_{}_", name.to_ascii_uppercase())
146 };
147 let url = if name == "default" {
148 env::var("DATABASE_URL")
149 .or_else(|_| env::var(format!("{prefix}URL")))
150 .unwrap_or_else(|_| {
151 "postgres://postgres:postgres@localhost:5432/anvil".to_string()
152 })
153 } else {
154 env::var(format!("{prefix}URL")).unwrap_or_default()
155 };
156
157 let pool_size = if name == "default" {
158 env::var("DB_POOL")
159 .or_else(|_| env::var(format!("{prefix}POOL")))
160 .ok()
161 .and_then(|v| v.parse().ok())
162 .unwrap_or(10)
163 } else {
164 env::var(format!("{prefix}POOL"))
165 .ok()
166 .and_then(|v| v.parse().ok())
167 .unwrap_or(10)
168 };
169
170 let read_urls = env::var(format!("{prefix}READ_URLS"))
171 .map(|s| {
172 s.split(',')
173 .map(|t| t.trim().to_string())
174 .filter(|t| !t.is_empty())
175 .collect()
176 })
177 .unwrap_or_default();
178
179 let driver_str = env::var(format!("{prefix}DRIVER")).unwrap_or_else(|_| {
180 if url.starts_with("postgres://") || url.starts_with("postgresql://") {
182 "postgres".into()
183 } else {
184 "postgres".into()
185 }
186 });
187 let driver = match driver_str.as_str() {
188 "postgres" | "pgsql" | "pg" => ConnectionDriver::Postgres,
189 other => ConnectionDriver::Other(other.to_string()),
190 };
191
192 Self {
193 driver,
194 url,
195 read_urls,
196 pool_size,
197 }
198 }
199}
200
201#[derive(Debug, Clone)]
202pub struct SessionConfig {
203 pub driver: String,
204 pub lifetime_minutes: i64,
205 pub cookie_name: String,
206 pub same_site: String,
207 pub secure: bool,
208}
209
210impl SessionConfig {
211 pub fn from_env() -> Self {
212 Self {
213 driver: env::var("SESSION_DRIVER").unwrap_or_else(|_| "file".to_string()),
214 lifetime_minutes: env::var("SESSION_LIFETIME")
215 .ok()
216 .and_then(|v| v.parse().ok())
217 .unwrap_or(120),
218 cookie_name: env::var("SESSION_COOKIE").unwrap_or_else(|_| "anvil_session".to_string()),
219 same_site: env::var("SESSION_SAME_SITE").unwrap_or_else(|_| "lax".to_string()),
220 secure: env::var("SESSION_SECURE")
221 .ok()
222 .and_then(|v| v.parse().ok())
223 .unwrap_or(false),
224 }
225 }
226}
227
228#[derive(Debug, Clone)]
229pub struct CacheConfig {
230 pub driver: String,
231 pub ttl_seconds: u64,
232}
233
234impl CacheConfig {
235 pub fn from_env() -> Self {
236 Self {
237 driver: env::var("CACHE_DRIVER").unwrap_or_else(|_| "moka".to_string()),
238 ttl_seconds: env::var("CACHE_TTL")
239 .ok()
240 .and_then(|v| v.parse().ok())
241 .unwrap_or(3600),
242 }
243 }
244}
245
246#[derive(Debug, Clone)]
247pub struct QueueConfig {
248 pub driver: String,
249 pub default_queue: String,
250}
251
252impl QueueConfig {
253 pub fn from_env() -> Self {
254 Self {
255 driver: env::var("QUEUE_DRIVER").unwrap_or_else(|_| "database".to_string()),
256 default_queue: env::var("QUEUE_DEFAULT").unwrap_or_else(|_| "default".to_string()),
257 }
258 }
259}
260
261#[derive(Debug, Clone)]
262pub struct MailConfig {
263 pub mailer: String,
264 pub host: String,
265 pub port: u16,
266 pub username: String,
267 pub password: String,
268 pub from_address: String,
269 pub from_name: String,
270}
271
272impl MailConfig {
273 pub fn from_env() -> Self {
274 Self {
275 mailer: env::var("MAIL_MAILER").unwrap_or_else(|_| "smtp".to_string()),
276 host: env::var("MAIL_HOST").unwrap_or_else(|_| "localhost".to_string()),
277 port: env::var("MAIL_PORT")
278 .ok()
279 .and_then(|v| v.parse().ok())
280 .unwrap_or(1025),
281 username: env::var("MAIL_USERNAME").unwrap_or_default(),
282 password: env::var("MAIL_PASSWORD").unwrap_or_default(),
283 from_address: env::var("MAIL_FROM_ADDRESS")
284 .unwrap_or_else(|_| "hello@example.com".to_string()),
285 from_name: env::var("MAIL_FROM_NAME").unwrap_or_else(|_| "Anvil".to_string()),
286 }
287 }
288}
289
290#[derive(Debug, Clone)]
291pub struct FilesystemConfig {
292 pub default_disk: String,
293 pub local_root: String,
294}
295
296impl FilesystemConfig {
297 pub fn from_env() -> Self {
298 Self {
299 default_disk: env::var("FILESYSTEM_DISK").unwrap_or_else(|_| "local".to_string()),
300 local_root: env::var("FILESYSTEM_LOCAL_ROOT")
301 .unwrap_or_else(|_| "storage/app".to_string()),
302 }
303 }
304}