Keys are now stored as a SHA-256 digest in the existing key column (64
chars, no schema change). Lookups hash the presented key; rows still
holding plaintext are matched and upgraded to their hash on first use.
The full key is shown once at creation and the dashboard displays a
masked placeholder thereafter.
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.
Introduces a SystemSettings model to manage global configuration, including user registration status. Admins can now enable or disable new user registration from the admin panel, and registration routes respect this setting. UI updates provide controls for toggling registration.
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.
Introduces Discord webhook notifications for flagged or rejected content. Adds project-level settings for Discord notifications, including enabling/disabling and specifying a webhook URL. Implements backend logic to send notifications and a frontend UI for configuring and testing Discord webhooks in project settings.
Integrates Google OAuth for user authentication, including login, account creation, and linking to existing accounts. Updates User model to support Google ID, adds relevant database service methods, and modifies login template to include Google sign-in. Also adds necessary configuration for Google OAuth and middleware for proxy headers.
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.