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
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
All write endpoints and the manage
routes are gated behind HTTP Basic Auth middleware; credentials are
configured via [auth] in config.toml or APP__AUTH__USERNAME /
APP__AUTH__PASSWORD environment variables.
Adds a server-side HTML management page and supporting backend for
full CRUD on emotes. The /manage prefix is kept isolated so an auth
middleware can be applied to it later without touching other routes.
- GET /manage — serves the management UI (add, edit, delete, search)
- GET /manage/emotes — admin JSON endpoint with uuid and alias included
- AdminEmoteResponse model for the expanded admin representation
- Client-side search filters by name or alias as you type