Files
mikebase/src/main.rs
T
Ganonmaster 2c219f5565 Fix S3 collision, alias clearing, upload limit, TOCTOU; add tests
Bug fixes:
- S3 key is now emoji/{uuid}.{ext} instead of emoji/{filename},
  preventing silent overwrites when two emotes share a filename
- UpdateEmoteRequest.alias uses Option<Option<String>> with a custom
  deserializer so a JSON null clears the alias rather than being ignored;
  the manage UI now sends null when the alias field is emptied
- POST /emotes is limited to 8 MiB via DefaultBodyLimit
- update_emote replaced the fetch-then-update pair with a single
  UPDATE … RETURNING using COALESCE/CASE WHEN, eliminating the TOCTOU
  race between concurrent edits

Refactoring:
- Extracted src/lib.rs so domain logic is a library crate; src/main.rs
  is now a thin startup entry point
- auth::check decoupled from AppState — takes Option<&AuthConfig> directly
- Removed unused config field from Database struct

Tests (40 total):
- auth: 10 unit tests covering all check() branches
- models: 6 unit tests for timestamp parsing and alias deserialization
- db: 9 unit tests against in-memory SQLite covering full CRUD
- routes: 15 integration tests in tests/routes.rs covering auth
  middleware, input validation, and all mutating endpoints
2026-04-28 11:31:25 +02:00

45 lines
1.5 KiB
Rust

use std::sync::Arc;
use mikebase::{config::AppConfig, db::Database, storage::S3Storage, AppState, build_router};
use sqlx::any::install_default_drivers;
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
#[tokio::main]
async fn main() {
// Initialise structured logging.
tracing_subscriber::registry()
.with(
tracing_subscriber::EnvFilter::try_from_default_env()
.unwrap_or_else(|_| "mikebase=debug,tower_http=debug".into()),
)
.with(tracing_subscriber::fmt::layer())
.init();
// Load configuration from config.toml / environment variables.
let cfg = AppConfig::load().expect("Failed to load configuration");
let cfg = Arc::new(cfg);
// Install SQLite and PostgreSQL drivers for sqlx AnyPool.
install_default_drivers();
// Connect to the database and run migrations.
let db = Database::connect(&cfg.database.url)
.await
.expect("Failed to connect to database");
db.migrate().await.expect("Failed to run migrations");
// Build S3 storage client.
let storage = S3Storage::new(&cfg);
let state = AppState { db, storage, cfg: cfg.clone() };
let addr = format!("{}:{}", cfg.server.host, cfg.server.port);
tracing::info!("Listening on {addr}");
let listener = tokio::net::TcpListener::bind(&addr)
.await
.expect("Failed to bind address");
axum::serve(listener, build_router(state))
.await
.expect("Server error");
}