Users management¶
Purpose¶
The Users screen is the operator's master directory of every player on the platform: search, filter, paginate, and click into a user to act on them. It is the gateway to all per-user actions documented in user-profile.md. Searches and listings are page-cursor-based; the same query options are reused by the user-picker on the affiliates and tips screens.
Audience¶
Customer support (primary), compliance (KYC reviews), operations (bans / withdrawal blocks). Engineering rarely touches this screen.
Path in admin-fe¶
| Screen | URL | Page | Component |
|---|---|---|---|
| List | /users |
ebit-admin-fe/src/app/(dashboard)/users/page.tsx:8 |
components/pages/users/index.tsx |
| Filters | (in-page) | components/pages/users/UsersFilters/ |
server-side filters |
| Table | (in-page) | components/pages/users/UsersTable/ |
row click → /users/[id] |
| Toolbar | (in-page) | components/pages/users/TableToolbar/ |
search + create-admin button |
Backing API endpoints¶
| Endpoint | Purpose | Source |
|---|---|---|
POST /admin/user/all |
Paginated user list with stats | apps/api/src/user/admin.user.controller.ts:57 |
GET /admin/user/:id |
Drill-in (used by /users/[id]) |
apps/api/src/user/admin.user.controller.ts:248 |
GET /admin/user/all-permissions |
Populates the permission-checkbox UI | apps/api/src/user/admin.user.controller.ts:82 |
GET /admin/user/get-all-roles-with-users |
Role filter dropdown | apps/api/src/user/admin.user.controller.ts:66 |
GET /admin/user/stats/transactions |
"Top users by transactions" sub-table | apps/api/src/user/admin.user.controller.ts:109 |
Frontend wiring: ebit-admin-fe/src/queries/users/index.ts — useAllUsersQuery, useGetUsersInfiniteQuery, useGetUserStatisticQuery.
Key actions¶
| Action | Required permission / role | API call | DB tables touched | Audit-logged? |
|---|---|---|---|---|
| List users (paginated, sorted) | user.view |
POST /admin/user/all |
User, UserRole, UserPermission, UserStats |
yes |
| Search by username / email / ID | user.view |
same, with search field |
same | yes |
| Filter by role (admin, streamer, banned…) | user.view |
same, with where.roles |
same | yes |
| Filter by KYC level | user.view + kyc.view |
same, with where.kycLevel |
same | yes |
| Sort by createdAt / balance / wagered USD | user.view |
same, with sortBy=ID|CREATED_AT|... |
same | yes |
| Open profile | user.view |
GET /admin/user/:id |
User, joined relations |
yes |
| Top users by tx | user.transactions.view |
GET /admin/user/stats/transactions |
Transaction, User |
yes |
AdminActionLog rows: every list/read writes a row keyed on userId/adminUserId. See data-model/ for AdminActionLog schema (api.prisma:1479-1497).
Filters and views¶
From UsersFilters/ and the TGetAllUsersOptions type (ebit-admin-fe/src/types):
- Search — username, email, ID, twitch handle.
- Role — admin, streamer, affiliate, banned, KYC-pending, etc.
- KYC level — Sumsub levels (0-3); see kyc-limits.md.
- Country — joined via the
Countrytable. - Date range — registration date (
createdAt). - Balance range — USD-equivalent.
- Wagered range — total wager USD.
- Sort —
EUserSortBy = ID | CREATED_AT | BALANCE | WAGERED_USD, asc/desc. withBalance/withStatsUsdtoggles — drop these to make the query 5× faster on broad filters; admin-fe sets themtrueby default.
Common workflows¶
- Customer support — find a user by email. CS receives a Zendesk ticket. They paste the email into the search box. The endpoint matches case-insensitive with
ILIKE. If multiple matches, sort bycreatedAt DESC. Click into the right user → see user-profile.md. - Compliance — review high-balance KYC-pending users. Filter
kycLevel=0AND sort byBALANCE DESC. Cross-reference top 50 with kyc-limits.md. Open each profile and callGET /admin/kyc/:userId/info. - Operations — find players from a restricted geography. Filter by
country=RU. Pull the list, ban or block withdrawals one by one — see user-profile.md. - Marketing — find top wagering players for VIP outreach. Sort by
WAGERED_USD DESC, take top 100. Forward to vip-program.md review queue. - Forensics — daily new-account scan. Filter
createdAt >= today, look for clusters of similar usernames or shared IPs (cross-check with admin-logs.md by IP).
Edge cases / gotchas¶
searchis case-insensitive but accent-sensitive. "müller" won't match "muller". This is a Prisma/Postgres limitation; deal with it by trying both spellings.withStatsUsd=trueis expensive. It joinsUserStatsand converts viaexchange-rates. On searches that return >5k rows this can blow the 5s frontend timeout. Narrow filters first.role=Bannedis a virtual filter. There is noBannedrole; the query filters onUser.bannedAt IS NOT NULL. Look for it inapps/api/src/user/user.service.ts(findManyUsersWithStats).- No bulk actions. Multi-select exists in the table UI (
SelectMore/) but bulk ban / bulk message is NOT IMPLEMENTED — seeadmin.user.controller.ts:147// TODO implement rout for multi ban. - The "Online" indicator is per-user, not aggregate. Profile-level
onlineflag is real (driven byAuth.session), but the count summary at the top of/usersreuses the inflatedfakeUserOnlineseries — seeproject_online_count_inflationmemory. - Pagination cursor is page-numbered, not
cursor-based. Stable while no inserts/deletes happen mid-scan. With heavy churn, you may see a row twice. Live with it; the alternative is unboundedcursorqueries.
Cross-links¶
- Per-user actions: user-profile.md
- Admin (employee) management variant of this screen: admins.md
- KYC details modal opens kyc-limits.md
- Withdrawal block toggle: withdrawals.md
- Notes module on the profile drawer: covered inline in user-profile.md
- Runbook:
runbooks/→ "user can't log in"
Sequence — opening the Users page and filtering¶
sequenceDiagram
actor admin
participant admin-fe
participant api
participant pg as Postgres
admin->>admin-fe: GET /users
admin-fe->>api: GET /admin/user/get-all-roles-with-users
api-->>admin-fe: roles
admin-fe->>api: GET /admin/user/all-permissions
api-->>admin-fe: permissions catalog (60+ keys)
admin->>admin-fe: pick role=Streamer, sort=BALANCE DESC
admin-fe->>api: POST /admin/user/all { sortOrder, sortBy, where }
api->>api: PermissionGuard('user.view')
api->>pg: SELECT user, JOIN userRole, userStats, balance
pg-->>api: rows + total count
api-->>admin-fe: { data: User[], paginationMeta }
admin-fe-->>admin: rendered table
admin->>admin-fe: click user row
admin-fe->>admin-fe: navigate /users/[id]
Note over admin-fe,api: see user-profile.md for the rest