Skip to main content

Members & Invites

Overview

The Members & Invites module provides team management for Lumio accounts. Account owners and administrators can invite users to their account, assign roles to members, and manage the team roster. The invite system supports invite links with configurable expiry and maximum uses, role assignment at invite time, and user search across the platform. Members can be part of multiple accounts, enabling account switching for multi-account users.

Architecture

Backend

  • GraphQL (apps/api/src/graphql/members.rs) -- Queries for listing members, listing invites, and searching users. Mutations for updating member roles, removing members, creating/deleting invites, and accepting invites.
  • Database (apps/api/src/db/members.rs) -- PostgreSQL operations for memberships, invites, user search, and platform connections lookup.
  • Permission Cache -- When a member's role is changed or a member is removed, the Redis permission cache is invalidated via lo_api::middleware::auth::invalidate_permission_cache().

Frontend

  • Team management page with member list and role badges.
  • 4-step invite wizard: select role, choose method (link/email/user search), configure options, copy invite link.
  • Account switcher for users with multiple account memberships.

API

GraphQL Queries

QueryPermissionDescription
membersmembers:readList all members of the current account (includes user info and role details)
invitesmembers:readList all invites for the current account
inviteByCode(code)Public (no auth)Public invite details (account name, role, expiry/use status) for the accept page
searchUsers(query, limit?)members:createSearch users by name, email, or platform username. Minimum 3 characters. Excludes existing account members. Max 10 results. Returns user info with platform connections.

GraphQL Mutations

MutationPermissionDescription
updateMemberRole(input: UpdateMemberRoleInput)members:editChange a member's role. Cannot change the account owner's role or assign the owner role. Invalidates Redis permission cache.
removeMember(membershipId: UUID)members:deleteRemove a member from the account. Cannot remove the account owner. Invalidates Redis permission cache.
createInvite(input: CreateInviteInput)members:createCreate an invite with role assignment, optional email/user targeting, max uses (default 1), and expiry (default 168 hours / 7 days). Cannot create invites with the owner role.
deleteInvite(inviteId: UUID)members:deleteDelete an invite
acceptInvite(code: String)AuthGuard (any authenticated user)Accept an invite by code. Validates expiry, max uses, and duplicate membership. Creates a new membership.

REST Endpoints

All paths live under /v1. Bodies are snake_case and mirror the GraphQL inputs.

MethodPathPermissionDescription
GET/v1/accounts/{id}/membersmembers:readList members of an account
PATCH/v1/accounts/{id}/members/{membership_id}/rolemembers:editChange a member's role
DELETE/v1/accounts/{id}/members/{membership_id}members:deleteRemove a member
POST/v1/invitesmembers:createCreate an invite (role + optional email/user + expiry)
GET/v1/invitesmembers:readList account invites
GET/v1/invites/{code}/infoAuthPublic lookup used by the accept page
DELETE/v1/invites/{id}members:deleteRevoke an invite
POST/v1/invites/{code}/acceptAuthAccept an invite as the authenticated user

Input Types

CreateInviteInput:

FieldTypeDefaultDescription
roleIdUUID(required)Role to assign to the invited user
emailString?nullTarget email address
invitedUserIdUUID?nullTarget user ID (from user search)
maxUsesi32?1Maximum number of times the invite can be used
expiresInHoursi32?168 (7 days)Expiry duration in hours

Permissions

PermissionDescription
members:readView member list and invites
members:createCreate invites, search users
members:editChange member roles
members:deleteRemove members, revoke invites

Database

TableDatabaseDescription
account_membershipsPostgreSQLLinks users to accounts. Fields: id, user_id, account_id, role_id, joined_at
account_invitesPostgreSQLInvite records. Fields: id, account_id, invited_by, invited_user_id, email, invite_code (unique), role_id, max_uses, use_count, expires_at, accepted_at, created_at
usersPostgreSQLUser profiles (display_name, email, avatar_url)
login_connectionsPostgreSQLPlatform connections for user search results (provider, username)

Data Flow

  1. Administrator creates an invite via the 4-step wizard, selecting a role and configuring options.
  2. An invite record is created with a unique invite_code.
  3. The invite link is shared with the target user.
  4. Target user clicks the link, which calls acceptInvite(code).
  5. The system validates the invite (expiry, max uses, no duplicate membership).
  6. A new account_membership record is created, linking the user to the account with the specified role.
  7. The invite use_count is incremented and accepted_at is set.

Role Change Flow

  1. Admin calls updateMemberRole with the membership ID and new role ID.
  2. System validates: membership belongs to account, target is not the owner, role exists and is not "owner".
  3. Role is updated in the database.
  4. Redis permission cache is invalidated for the affected user/account pair.

Key Files

PathDescription
apps/api/src/graphql/members.rsGraphQL queries and mutations
apps/api/src/db/members.rsDatabase operations for memberships and invites
apps/api/src/db/roles.rsRole lookup for validation