Files
mikebase/src/config.rs
T
Ganonmaster 9c5212de05 feat: add emote import from legacy source with dry-run mode
Add POST /manage/import (auth-protected) that fetches emotes from a
legacy JSON endpoint, downloads each image, uploads it to S3, and
inserts it into the DB with the original timestamps preserved.

- Skip emotes whose name already exists (best-effort duplicate detection
  across SQLite and PostgreSQL via error code + message fallback)
- Validate source_url against a configurable host allowlist
  ([import] allowed_hosts in config, default ["smutba.se"])
- dry_run: true previews the import without writing to S3 or DB;
  result statuses are "would_import" / "would_skip" instead of
  "imported" / "skipped"
- Add db.name_exists() for efficient per-name existence checks used
  by dry-run
- Add reqwest (rustls-tls + json) and url dependencies
- Integration tests: auth guard, allowlist rejection, mirror +
  skip-duplicates, dry-run no-persist
2026-05-02 02:48:24 +02:00

100 lines
2.3 KiB
Rust

use config::{Config, ConfigError, Environment, File};
use serde::Deserialize;
#[derive(Debug, Deserialize, Clone)]
pub struct S3Config {
pub endpoint: String,
pub region: String,
pub bucket: String,
pub access_key: String,
pub secret_key: String,
/// Public base URL used to build emote image URLs returned in API responses.
/// Example: "https://s3.eu-central-1.wasabisys.com/open3dlab-emoji"
pub public_url: String,
}
#[derive(Debug, Deserialize, Clone)]
pub struct DatabaseConfig {
/// Database URL.
/// SQLite example: "sqlite://mikebase.db"
/// PostgreSQL example: "postgresql://user:pass@localhost/mikebase"
pub url: String,
}
#[derive(Debug, Deserialize, Clone)]
pub struct ServerConfig {
#[serde(default = "default_host")]
pub host: String,
#[serde(default = "default_port")]
pub port: u16,
}
fn default_host() -> String {
"0.0.0.0".to_string()
}
fn default_port() -> u16 {
3000
}
#[derive(Debug, Deserialize, Clone)]
pub struct AuthConfig {
pub username: String,
pub password: String,
}
#[derive(Debug, Deserialize, Clone)]
pub struct ImportConfig {
#[serde(default = "default_allowed_hosts")]
pub allowed_hosts: Vec<String>,
}
fn default_allowed_hosts() -> Vec<String> {
vec!["smutba.se".to_string()]
}
#[derive(Debug, Deserialize, Clone)]
pub struct AppConfig {
pub s3: S3Config,
pub database: DatabaseConfig,
#[serde(default)]
pub server: ServerConfig,
pub auth: Option<AuthConfig>,
#[serde(default)]
pub import: ImportConfig,
}
impl Default for ServerConfig {
fn default() -> Self {
Self {
host: default_host(),
port: default_port(),
}
}
}
impl Default for ImportConfig {
fn default() -> Self {
Self {
allowed_hosts: default_allowed_hosts(),
}
}
}
impl AppConfig {
pub fn load() -> Result<Self, ConfigError> {
let cfg = Config::builder()
// Optional config file (config.toml)
.add_source(File::with_name("config").required(false))
// Environment variables with prefix APP (e.g. APP__S3__BUCKET)
.add_source(
Environment::with_prefix("APP")
.separator("__")
.try_parsing(true),
)
.build()?;
cfg.try_deserialize()
}
}