Concurrency / performance
- Drop the global threading.RLock in DatabaseService. It was wrapping every
_safe_execute and turning the 8-wide thread pool into a 1-wide one;
Flask-SQLAlchemy's scoped_session already gives each thread its own
session, so the lock was protecting nothing while serialising every DB
op in the process.
- moderate_content now awaits _process_rules and _handle_no_matches; both
push the blocking OpenAI call to asyncio.to_thread so the event loop
stays responsive while waiting on the model.
- DiscordNotifier shares a single module-level requests.Session (instead
of one per instance) and exposes an async wrapper that runs the POST
via asyncio.to_thread, so the moderation request doesn't pin its loop
on Discord latency / 429 retry backoff.
- load_user simplified to a single User.query.get(user_id). Previously it
spun a fresh ThreadPoolExecutor and asyncio event loop on every
authenticated request just to await an async wrapper around the same
primary-key lookup.
- ResultCache now LRU-evicts the oldest entries when at capacity with no
expired entries to free, instead of silently dropping new writes.
_cleanup_expired_entries now sets _last_cleanup_time so the periodic
throttle applies even when called from the hot-path fallback.
Data integrity
- APIUser gained UniqueConstraint(project_id, external_user_id).
get_or_create_api_user catches IntegrityError, rolls back, and reads the
winner's row so concurrent submits for the same external user no longer
produce duplicate APIUser rows that split stats.
- ProjectMember gained UniqueConstraint(project_id, user_id). The
invitation accept flow catches the IntegrityError and treats it as a
benign duplicate-click.
- New scripts/dedup_before_unique_constraints.py to merge any pre-existing
duplicate rows so the new constraints can be added cleanly. Dry-run by
default, --apply to commit.
- Fix the API-user stats detached-instance bug: increment_api_user_stats
in the DB service now does query + mutation + commit in one
_safe_execute call. Previously the orchestrator mutated a detached ORM
instance outside any session and the changes silently never persisted,
so per-user counters drifted.
- Manual-review decision flow now commits the moderation result first,
then atomically commits the APIUser stat bump in the same request
context (instead of relying on the detached-instance pattern).
Correctness
- ContentType enum is now {text, markdown, html} to match the route's
whitelist. Previously the schema declared text/image/video/audio while
the route accepted text/markdown/html, so markdown/html submissions
died with a Pydantic 400 before reaching the route.
- Project invitation emails are normalised to lowercase on store and
compared case-insensitively on accept/decline. Mixed-case stored
invitations were silently locking recipients out of accepting their
own invite.
- Admin create_user lowercases the email and requires 8-char passwords
(matching the public registration policy). The previous 6-char floor
was weaker than the public path despite admin-created accounts often
being privileged.
- AI moderator response parsing now uses (content or "").strip() — OpenAI
returns content=None on safety refusals and the raw .strip() raised
AttributeError that the surrounding except didn't catch.
Improved APIUser.get_current_stats to use a single efficient query with caching for stats retrieval. Updated manual_review route to only display API users who have submitted content by joining with a content count subquery and filtering out users with zero content.
Project and moderation rule creation methods now return dictionaries instead of ORM instances to avoid detached instance issues. Updated related usages in dashboard route and default rules configuration to match new return types and argument names. Also added cascade delete-orphan to APIUser's project relationship.
APIUser model now computes stats directly from the database for accuracy, with new properties for current counts and days active. API documentation UI is improved with consistent code block styling, theme-aware code blocks, copy buttons, and example cards for cURL, JavaScript, and Python. Manual review templates updated to use new APIUser properties for displaying stats.
Eliminated unnecessary 'flask_sqlalchemy' imports from model files to clean up code. Updated flake8 max line length to 128 in pre-commit config. Improved error response formatting in manual_review route for better readability.
Reorganized imports and formatting across models and routes for better readability and maintainability. Improved consistency in model field definitions, relationship declarations, and route logic. No functional changes, but code is now easier to follow and modify.