Members & accounts
Currnt’s user model is the same users table for every role: visitor, member, expert, client, staff. Role distinction is by flags and a member_type enum, not by separate tables. This domain is everything that reads or writes that table outside of pure authentication.
What it covers
- Account list & detail — Treehouse views over the user base, with filters by role, signup source, activity recency, and engagement score.
- Public profile —
/profile/:usernamefor experts and members who’ve set theirs up. Sparse content; most profiles are placeholders. - Account settings —
/account/settings,/account/notifications,/account/preferences. Member-facing. - Segmentation — internal grouping for outreach (overlap with Treehouse Prospects pillar but at the user level, not the prospect level).
- Lifecycle ops — suspension, reactivation, deletion (soft only), and the merge workflow for collapsing duplicate accounts.
Representative routes
| Method | Path | Handler | Notes |
|---|---|---|---|
| GET | /admin/members | MemberController.list | Filter by role, status, signup date. |
| GET | /admin/members/:id | MemberController.detail | Activity timeline + boards + outreach history. |
| POST | /admin/members/:id/suspend | MemberController.suspend | Sets status; revokes sessions in Redis. |
| POST | /admin/members/:id/merge | MemberController.merge | Collapse two accounts into one; preserves email aliases. |
| GET | /admin/members/segments | SegmentController.list | Saved internal segments for outreach overlap. |
| GET | /profile/:username | ProfileController.show | Public profile page. |
| GET | /account/settings | AccountController.settings | |
| POST | /account/settings | AccountController.updateSettings | Email, name, password change. |
| GET | /account/notifications | AccountController.notifications | Per-channel opt-in flags. |
Headline flow: duplicate-account merge
Account dedup is the most-used operation in this domain. The CRM produces duplicates as a matter of course (same person, two emails); Treehouse’s merge flow collapses them without losing history.
Step 1: Staff at /admin/prospects flags a dedup candidate pair.
Step 2: Opens /admin/members/:id where MemberController.detail surfaces
likely-duplicate users (matched by email-domain + name fuzzy).
Step 3: Reviews activity on each side: boards joined, posts authored,
outreach received, last login.
Step 4: Selects "merge into this one" → POST /admin/members/:id/merge.
Step 5: MemberController.merge starts a MySQL transaction:
- Repoint all FK references on the loser row to the winner.
- Move email aliases (loser row's email → users.alt_emails JSON).
- Soft-delete the loser row (status='merged', merged_into=winner_id).
- Log merge event for audit.
Commits or rolls back atomically.
Step 6: Both URLs (winner + loser) now resolve to the winner; loser's old
email continues to work for login via the alias lookup.