User Moderation
Admins and moderators can search users, view their moderation history, and take action — warnings, suspensions, bans, and verification — from the admin panel.
Implementation checklist
Search and view users
- Search and list users with filters (
adminGetUsers- backend ready, wired to the users list page) - Get a user's moderation profile (
adminGetUserDetails- backend ready, wired to the user detail page) - Get a user's moderation action history (
adminGetUserModerationHistory- wired to the user detail page's "Historial de moderación" section) - Platform-wide user stats (
adminGetUserStats- wired to the users list page's stat cards) - The
adminGetSuspendedUsers,adminGetBannedUsers,adminGetUserSuspensions, andadminGetUserWarningsqueries have been removed from the schema (no longer inadmin/user-moderation.type.jsoradmin/user-moderation.resolver.js). The users list page still filters by status viaadminGetUsersinstead; per-user suspension/warning counts surface throughadminGetUserDetails(warningCount) and history throughadminGetUserModerationHistory. The manager still hasgetSuspendedUsers/getBannedUsers/getUserSuspensions/getUserWarningsmethods inuser-moderation.manager.js, but they're now unreachable dead code - no resolver calls them.
Suspend, warn, and ban
- Suspend a user (
adminSuspendUser- backend ready, wired to the user detail page) - Lift a suspension (
adminUnsuspendUser- backend ready, wired to the user detail page) - Issue a warning (
adminWarnUser- wired to the user detail page. Fixed this session: it passed the issuing admin's ID asissued_by, butuser-warnings.access-service.js'screate()only readsadminId/admin_id- the persisted warning'sadmin_idcolumn was alwaysNULL.) - Ban a user (
adminBanUser- wired to the user detail page. Fixed this session: when "also delete content" is requested it internally callsremoveUserContent, but was calling it with only 3 of its 5 arguments, socontextlanded in thereasonslot andadminId/contextwere bothundefineddownstream - harmless by coincidence sinceremoveUserContentdidn't use those params either, but a real signature mismatch, now passes all 5 in order.) - Reverse a ban (
adminUnbanUser- backend ready, wired to the user detail page) - Remove a user's content (posts/comments) (
adminRemoveUserContent- wired to the user detail page's "Eliminar contenido" action, with a content-type selector: ALL/POSTS/COMMENTS. Fixed this session: the POSTS branch calledpostAccessService.findByUser(), a method that doesn't exist (the real method isgetByUser) - the resulting throw was silently swallowed by the surrounding try/catch, which meantcontentType: 'ALL'silently skipped POSTS and every content type after it in the same try block, while still reportingsuccess: true. The selector previously also offered BLASTS/TALES/ARTICLES; those were dropped since they never had a real backend model - see theContentTypeenum note in the Content Moderation technical reference.)
Bulk moderation
- Bulk-suspend users (super_admin only) (
adminBulkSuspendUsers- wired to the users list page's bulk-select + "Suspender seleccionados" action) - Bulk-ban users (super_admin only) (
adminBulkBanUsers(userIds, reason, permanent)- resolver gated onisSuperAdmin→userModerationManager.bulkBanUsers, which callsbanUserper user withdeleteContent: false, notifyUser: false. Fixed this session: its return type wasModerationActionResponse(success/message/actionId), which doesn't expose thesuccessCount/failedCount/total/errorsthe manager already computes per user - widened to a newUserBulkOperationResponsetype (adminBulkSuspendUserswidened the same way, same latent gap). Wired to a "Banear seleccionados" button on the/userspage's bulk-select bar, super_admin only, next to the existing bulk-suspend action; a partial-failure result now shows an inline "N succeeded, M failed" message instead of a blind success.)
Verification (admin schema)
- Grant a verification badge (
adminVerifyUser- backend ready, wired to the user detail page) - Revoke a verification badge (
adminRemoveVerification- wired to the user detail page's "Quitar verificación" action, shown once a user is verified)
These are separate from the client-facing verification request flow (getPendingVerificationRequests / grantVerification / removeVerification / rejectVerificationRequest), which lives on the client schema and is documented in Verification. Both are real, independent implementations of similar functionality, on different schemas with different auth models.
Technical reference
See Admin Panel → User management for the full GraphQL API.