-
Notifications
You must be signed in to change notification settings - Fork 0
Content Moderation
User-submitted report text is scanned automatically on two layers:
-
Wordlist (
naughty-words) - offline multilingual filter, runs first on every row -
OpenAI Moderation API - semantic fallback for anything the wordlist misses (requires
OPENAI_API_KEYsecret in the proton-pulse-data repo; falls back to wordlist-only if absent)
The scan runs every 4 hours via GitHub Actions (content-moderation.yml) with a 5-hour lookback window, and does a full 25-hour sweep daily at 02:00 UTC. Flagged reports are hidden from public views automatically. Report authors see a "Flagged" badge on their profile page with a plain-language explanation and a link to the Discord server for disputes.
To trigger a manual scan:
gh workflow run content-moderation.yml --repo mdeguzis/proton-pulse-data \
-f dry_run=true -f lookback_hours=720The public.admins table controls who can access the admin panel at /admin.html. There are two roles.
This is the default role when a user is added to the admins table. Moderators can:
- View and manage flagged reports (hide, unhide, unflag)
- View and manage the banned users list (add, remove bans)
- View and search banned phrases
Moderators cannot add or remove other admins, and cannot change anyone's role.
Super admins have all moderator permissions plus:
- Add new admins (assign moderator or super_admin role)
- Change an existing admin's role
- Remove admins from the table
- Add and remove banned phrases
Only super admins can modify the admins table. The RLS policies on public.admins enforce this at the database level via the is_current_user_super_admin() SECURITY DEFINER function.
Role checks happen in two places:
Database (RLS policies)
-
is_current_user_admin()- SECURITY DEFINER function, returns true if the calling user'sauth.uid()is present inpublic.adminsregardless of role. Used to gate read access to the admins, banned_users, and banned_phrases tables. -
is_current_user_super_admin()- same pattern but also checksrole = 'super_admin'. Used to gate INSERT/UPDATE/DELETE on the admins table.
These functions bypass RLS internally to avoid infinite recursion (PostgreSQL 42P17).
Frontend (admin panel)
admin.html checks whether the signed-in user has a row in public.admins before revealing the panel. If the check fails (not in the table), a "not authorized" message is shown instead.
The admin nav link in the top bar (topbar.js) does the same check and stays hidden for non-admins.
Only a super admin can do this through the admin panel. The panel's "Admins" tab has a form with fields for the user's UUID (from auth.users), their Steam username, and the role to assign.
To find a user's UUID: look it up in the Supabase dashboard under Authentication > Users, or have the user check their profile page on proton-pulse.com.
When a user is added to public.banned_users, a trigger (trg_hide_configs_on_ban) automatically sets is_hidden = true on all their user_configs rows. This removes their reports from public views immediately without needing a separate moderation step.
Banned users are matched by proton_pulse_user_id (for signed-in users) or client_id (for anonymous submissions).