use crate::{Error, Result};
use std::{
fs,
io::Write,
path::{Path, PathBuf},
};
pub const ENV_FILE: &str = ".env";
pub const DEFAULT_ENV_TEMPLATE: &str = "\
# FoukoApi configuration. Fill in whatever you need and remove the rest.
# --- Platforms --------------------------------------------------------------
# Telegram bot token (from @BotFather). Leave empty to disable Telegram.
TG_TOKEN=
# Discord bot token. Leave empty to disable Discord.
DISCORD_TOKEN=
# --- Database ---------------------------------------------------------------
# Storage backend URL. Supported schemes:
# sqlite:./foukobot.sqlite local SQLite file (auto-created)
# memory: in-memory, lost on restart
# postgres://user:pass@host/db connect to an external Postgres instance
# FOUKO_DB=sqlite:./foukobot.sqlite
# --- Logging ----------------------------------------------------------------
# RUST_LOG=info,foukoapi=info
";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EnvState {
AlreadyExists,
Created,
}
pub fn bootstrap_env() -> Result<EnvState> {
bootstrap_env_at(Path::new(ENV_FILE))
}
pub fn bootstrap_env_at(path: &Path) -> Result<EnvState> {
let state = if path.exists() {
EnvState::AlreadyExists
} else {
write_template(path)?;
EnvState::Created
};
let _ = dotenvy::from_path(path);
Ok(state)
}
fn write_template(path: &Path) -> Result<()> {
let mut f = fs::File::create(path)
.map_err(|e| Error::Other(format!("could not create {}: {e}", path.display())))?;
f.write_all(DEFAULT_ENV_TEMPLATE.as_bytes())
.map_err(|e| Error::Other(format!("could not write {}: {e}", path.display())))?;
Ok(())
}
#[derive(Debug, Clone)]
pub enum DbUrl {
Sqlite(PathBuf),
Memory,
External(String),
}
impl DbUrl {
pub fn parse(url: &str) -> Result<Self> {
let url = url.trim();
if url.is_empty()
|| url.eq_ignore_ascii_case("memory:")
|| url.eq_ignore_ascii_case("memory")
{
return Ok(Self::Memory);
}
if let Some(rest) = url
.strip_prefix("sqlite:")
.or_else(|| url.strip_prefix("sqlite://"))
{
if rest.is_empty() {
return Err(Error::Other("sqlite URL is missing a path".into()));
}
return Ok(Self::Sqlite(PathBuf::from(rest)));
}
Ok(Self::External(url.to_owned()))
}
pub fn from_env() -> Result<Self> {
match std::env::var("FOUKO_DB") {
Ok(v) if !v.trim().is_empty() => Self::parse(&v),
_ => Ok(Self::Sqlite(default_sqlite_path())),
}
}
}
fn default_sqlite_path() -> PathBuf {
const FILE_NAME: &str = "foukoapi.sqlite";
if let Ok(exe) = std::env::current_exe() {
if let Some(dir) = exe.parent() {
return dir.join(FILE_NAME);
}
}
PathBuf::from(FILE_NAME)
}