Skip to main content

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, and adminGetUserWarnings queries have been removed from the schema (no longer in admin/user-moderation.type.js or admin/user-moderation.resolver.js). The users list page still filters by status via adminGetUsers instead; per-user suspension/warning counts surface through adminGetUserDetails (warningCount) and history through adminGetUserModerationHistory. The manager still has getSuspendedUsers/getBannedUsers/getUserSuspensions/getUserWarnings methods in user-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 as issued_by, but user-warnings.access-service.js's create() only reads adminId/admin_id - the persisted warning's admin_id column was always NULL.)
  • Ban a user (adminBanUser - wired to the user detail page. Fixed this session: when "also delete content" is requested it internally calls removeUserContent, but was calling it with only 3 of its 5 arguments, so context landed in the reason slot and adminId/context were both undefined downstream - harmless by coincidence since removeUserContent didn'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 called postAccessService.findByUser(), a method that doesn't exist (the real method is getByUser) - the resulting throw was silently swallowed by the surrounding try/catch, which meant contentType: 'ALL' silently skipped POSTS and every content type after it in the same try block, while still reporting success: true. The selector previously also offered BLASTS/TALES/ARTICLES; those were dropped since they never had a real backend model - see the ContentType enum 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 on isSuperAdminuserModerationManager.bulkBanUsers, which calls banUser per user with deleteContent: false, notifyUser: false. Fixed this session: its return type was ModerationActionResponse (success/message/actionId), which doesn't expose the successCount/failedCount/total/errors the manager already computes per user - widened to a new UserBulkOperationResponse type (adminBulkSuspendUsers widened the same way, same latent gap). Wired to a "Banear seleccionados" button on the /users page'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)

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.