Context
Derived from the audit in #205. Depends on #229 (admin_auth_metadata.last_active_at must exist and be populated before this endpoint can return meaningful data).
No endpoint exists to list who is currently on the admin team. This is the last missing piece of the team management surface.
What's needed
GET /admin/teams/:teamId/members
Paginated list of current team members.
Response shape per member:
{
id: string // team_memberships.id
user_id: string
full_name: string // from users
email: string // from users
role: string // from team_memberships
joined_at: Date // from team_memberships
last_active_at: Date | null // from admin_auth_metadata
}
Query params: page, limit (via existing PaginationDto)
Implementation:
- Query builder joining
team_memberships → users → admin_auth_metadata (LEFT JOIN on admin_auth_metadata — member may not have one yet if they've never made a request)
- Order by
joined_at DESC by default
- Scoped to members where
users.is_active = true and users.deleted_at IS NULL
TeamMembersListAction
Raw query builder action (similar pattern to AdminUsersListAction) rather than going through AbstractModelAction — the three-table join with a nullable left join makes the generic API awkward here.
Acceptance criteria
Depends on
#229
Context
Derived from the audit in #205. Depends on #229 (
admin_auth_metadata.last_active_atmust exist and be populated before this endpoint can return meaningful data).No endpoint exists to list who is currently on the admin team. This is the last missing piece of the team management surface.
What's needed
GET /admin/teams/:teamId/membersPaginated list of current team members.
Response shape per member:
Query params:
page,limit(via existingPaginationDto)Implementation:
team_memberships → users → admin_auth_metadata(LEFT JOIN onadmin_auth_metadata— member may not have one yet if they've never made a request)joined_at DESCby defaultusers.is_active = trueandusers.deleted_at IS NULLTeamMembersListActionRaw query builder action (similar pattern to
AdminUsersListAction) rather than going throughAbstractModelAction— the three-table join with a nullable left join makes the generic API awkward here.Acceptance criteria
last_active_atisnullfor members who have never made an authenticated admin requestDepends on
#229