package support import ( "context" "errors" "fmt" "github.com/descrybe/descrybe-v2/apps/api/internal/auth" "github.com/google/uuid" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgconn" ) // ListAgents returns support_staff users and optionally full-admin staff. func (s *Service) ListAgents(ctx context.Context, includePlatformAdmins bool, limit, offset int) ([]SupportAgent, int64, error) { limit, offset = clampListBounds(limit, offset) where := `(staff_role = 'support_staff'` if includePlatformAdmins { where += ` OR staff_role IN ('admin','developer') OR (is_platform_admin = true AND (staff_role IS NULL OR staff_role = ''))` } where += `) AND is_active = true` var total int64 if err := s.Pool.QueryRow(ctx, `SELECT count(*) FROM users WHERE `+where).Scan(&total); err != nil { if isUndefinedColumn(err) { return []SupportAgent{}, 0, nil } return nil, 0, err } q := fmt.Sprintf(` SELECT id, email, COALESCE(name, ''), is_platform_admin, COALESCE(staff_role, ''), is_active FROM users WHERE %s ORDER BY email ASC LIMIT $1 OFFSET $2`, where) rows, err := s.Pool.Query(ctx, q, limit, offset) if err != nil { if isUndefinedColumn(err) { return []SupportAgent{}, 0, nil } return nil, 0, err } defer rows.Close() out := make([]SupportAgent, 0, limit) for rows.Next() { var a SupportAgent var role string if err := rows.Scan(&a.ID, &a.Email, &a.Name, &a.IsPlatformAdmin, &role, &a.IsActive); err != nil { return nil, 0, err } a.StaffRole = role access := auth.ResolveStaffAccess(a.IsPlatformAdmin, role) a.IsSupportAgent = access.SupportDesk a.IsPlatformAdmin = access.FullAdmin out = append(out, a) } return out, total, rows.Err() } // SetSupportAgent grants or revokes staff_role=support_staff (does not grant full admin). func (s *Service) SetSupportAgent(ctx context.Context, userID uuid.UUID, enable bool) (SupportAgent, error) { var email, name string var isAdmin, isActive bool var staffRole *string err := s.Pool.QueryRow(ctx, ` SELECT email, COALESCE(name, ''), is_platform_admin, staff_role, is_active FROM users WHERE id = $1`, userID, ).Scan(&email, &name, &isAdmin, &staffRole, &isActive) if errors.Is(err, pgx.ErrNoRows) { return SupportAgent{}, ErrNotFound } if err != nil { return SupportAgent{}, err } role := "" if staffRole != nil { role = *staffRole } access := auth.ResolveStaffAccess(isAdmin, role) if enable { if access.FullAdmin { // Already has desk via admin/developer — leave role unchanged. } else { _, err = s.Pool.Exec(ctx, ` UPDATE users SET staff_role = $2, is_platform_admin = true, updated_at = now() WHERE id = $1`, userID, auth.StaffRoleSupportStaff) if err != nil { return SupportAgent{}, err } role = auth.StaffRoleSupportStaff isAdmin = true } } else { if role == auth.StaffRoleSupportStaff { _, err = s.Pool.Exec(ctx, ` UPDATE users SET staff_role = NULL, is_platform_admin = false, updated_at = now() WHERE id = $1`, userID) if err != nil { return SupportAgent{}, err } role = "" isAdmin = false } // Do not demote admin/developer via this endpoint. } access = auth.ResolveStaffAccess(isAdmin, role) return SupportAgent{ ID: userID, Email: email, Name: name, IsSupportAgent: access.SupportDesk, IsPlatformAdmin: access.FullAdmin, StaffRole: role, IsActive: isActive, }, nil } func isUndefinedColumn(err error) bool { var pgErr *pgconn.PgError if errors.As(err, &pgErr) { return pgErr.Code == "42703" } return false }