2c219f5565
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
45 lines
1.5 KiB
Rust
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");
|
|
}
|