9c5212de05
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
100 lines
2.3 KiB
Rust
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()
|
|
}
|
|
}
|