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

Representative routes

Members routes — internal admin + member-facing settings.
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.