|
| 1 | +import Combine |
| 2 | +import StytchCore |
| 3 | +import UIKit |
| 4 | + |
| 5 | +final class OrganizationMemberSearchViewController: UIViewController { |
| 6 | + private let stackView = UIStackView.stytchB2BStackView() |
| 7 | + |
| 8 | + private lazy var searchAllMembersButton: UIButton = .init(title: "Search all members", primaryAction: .init { [weak self] _ in |
| 9 | + self?.searchAllMembers() |
| 10 | + }) |
| 11 | + |
| 12 | + private lazy var searchBySingleEmailButton: UIButton = .init(title: "Search by single email", primaryAction: .init { [weak self] _ in |
| 13 | + self?.searchBySingleEmail() |
| 14 | + }) |
| 15 | + private lazy var searchByMultipleEmailsButton: UIButton = .init(title: "Search by multiple emails", primaryAction: .init { [weak self] _ in |
| 16 | + self?.searchByMultipleEmails() |
| 17 | + }) |
| 18 | + private lazy var searchByEmailFuzzyButton: UIButton = .init(title: "Search by email fuzzy", primaryAction: .init { [weak self] _ in |
| 19 | + self?.searchByEmailFuzzy() |
| 20 | + }) |
| 21 | + private lazy var searchByBreakglassTrueButton: UIButton = .init(title: "Search breakglass true", primaryAction: .init { [weak self] _ in |
| 22 | + self?.searchByBreakglassTrue() |
| 23 | + }) |
| 24 | + private lazy var searchByPasswordExistsFalseButton: UIButton = .init(title: "Search password exists false", primaryAction: .init { [weak self] _ in |
| 25 | + self?.searchByPasswordExistsFalse() |
| 26 | + }) |
| 27 | + private lazy var searchByStatusesButton: UIButton = .init(title: "Search by statuses", primaryAction: .init { [weak self] _ in |
| 28 | + self?.searchByStatuses() |
| 29 | + }) |
| 30 | + private lazy var searchByRolesButton: UIButton = .init(title: "Search by roles", primaryAction: .init { [weak self] _ in |
| 31 | + self?.searchByRoles() |
| 32 | + }) |
| 33 | + private lazy var searchAndEmailsAndPasswordButton: UIButton = .init(title: "Search AND emails and password", primaryAction: .init { [weak self] _ in |
| 34 | + self?.searchAndEmailsAndPassword() |
| 35 | + }) |
| 36 | + private lazy var searchOrFuzzyEmailOrFuzzyPhoneButton: UIButton = .init(title: "Search OR fuzzy email or fuzzy phone", primaryAction: .init { [weak self] _ in |
| 37 | + self?.searchOrFuzzyEmailOrFuzzyPhone() |
| 38 | + }) |
| 39 | + private lazy var searchWithPaginationSmallLimitButton: UIButton = .init(title: "Search with small limit for pagination", primaryAction: .init { [weak self] _ in |
| 40 | + self?.searchWithPaginationSmallLimit() |
| 41 | + }) |
| 42 | + |
| 43 | + override func viewDidLoad() { |
| 44 | + super.viewDidLoad() |
| 45 | + |
| 46 | + title = "Organization Member Search" |
| 47 | + view.backgroundColor = .systemBackground |
| 48 | + |
| 49 | + view.addSubview(stackView) |
| 50 | + stackView.translatesAutoresizingMaskIntoConstraints = false |
| 51 | + NSLayoutConstraint.activate([ |
| 52 | + stackView.leadingAnchor.constraint(equalTo: view.leadingAnchor), |
| 53 | + stackView.trailingAnchor.constraint(equalTo: view.trailingAnchor), |
| 54 | + stackView.centerYAnchor.constraint(equalTo: view.centerYAnchor), |
| 55 | + ]) |
| 56 | + |
| 57 | + // Buttons |
| 58 | + stackView.addArrangedSubview(searchAllMembersButton) |
| 59 | + stackView.addArrangedSubview(searchBySingleEmailButton) |
| 60 | + stackView.addArrangedSubview(searchByMultipleEmailsButton) |
| 61 | + stackView.addArrangedSubview(searchByEmailFuzzyButton) |
| 62 | + stackView.addArrangedSubview(searchByBreakglassTrueButton) |
| 63 | + stackView.addArrangedSubview(searchByPasswordExistsFalseButton) |
| 64 | + stackView.addArrangedSubview(searchByStatusesButton) |
| 65 | + stackView.addArrangedSubview(searchByRolesButton) |
| 66 | + stackView.addArrangedSubview(searchAndEmailsAndPasswordButton) |
| 67 | + stackView.addArrangedSubview(searchOrFuzzyEmailOrFuzzyPhoneButton) |
| 68 | + stackView.addArrangedSubview(searchWithPaginationSmallLimitButton) |
| 69 | + } |
| 70 | + |
| 71 | + // MARK: - Demo search builders |
| 72 | + |
| 73 | + private func searchAllMembers() { |
| 74 | + Task { |
| 75 | + let operands: [any SearchQueryOperand] = [] |
| 76 | + let queryOperator: StytchB2BClient.Organizations.SearchParameters.SearchOperator = .AND |
| 77 | + await performSearch(operands: operands, operator: queryOperator, cursor: nil, limit: nil, label: "all members") |
| 78 | + } |
| 79 | + } |
| 80 | + |
| 81 | + private func searchBySingleEmail() { |
| 82 | + Task { |
| 83 | + let operands = makeOperands([ |
| 84 | + (.memberEmails, ["foo@stytch.com"]), |
| 85 | + ]) |
| 86 | + let queryOperator: StytchB2BClient.Organizations.SearchParameters.SearchOperator = .AND |
| 87 | + await performSearch(operands: operands, operator: queryOperator, cursor: nil, limit: nil, label: "single email") |
| 88 | + } |
| 89 | + } |
| 90 | + |
| 91 | + private func searchByMultipleEmails() { |
| 92 | + Task { |
| 93 | + let operands = makeOperands([ |
| 94 | + (.memberEmails, ["alpha@example.com", "beta@example.com"]), |
| 95 | + ]) |
| 96 | + let queryOperator: StytchB2BClient.Organizations.SearchParameters.SearchOperator = .OR |
| 97 | + await performSearch(operands: operands, operator: queryOperator, cursor: nil, limit: nil, label: "multiple emails OR") |
| 98 | + } |
| 99 | + } |
| 100 | + |
| 101 | + private func searchByEmailFuzzy() { |
| 102 | + Task { |
| 103 | + let operands = makeOperands([ |
| 104 | + (.memberEmailFuzzy, "ali"), |
| 105 | + ]) |
| 106 | + let queryOperator: StytchB2BClient.Organizations.SearchParameters.SearchOperator = .AND |
| 107 | + await performSearch(operands: operands, operator: queryOperator, cursor: nil, limit: nil, label: "email fuzzy") |
| 108 | + } |
| 109 | + } |
| 110 | + |
| 111 | + private func searchByBreakglassTrue() { |
| 112 | + Task { |
| 113 | + let operands = makeOperands([ |
| 114 | + (.memberIsBreakglass, true), |
| 115 | + ]) |
| 116 | + let queryOperator: StytchB2BClient.Organizations.SearchParameters.SearchOperator = .AND |
| 117 | + await performSearch(operands: operands, operator: queryOperator, cursor: nil, limit: nil, label: "breakglass true") |
| 118 | + } |
| 119 | + } |
| 120 | + |
| 121 | + private func searchByPasswordExistsFalse() { |
| 122 | + Task { |
| 123 | + let operands = makeOperands([ |
| 124 | + (.memberPasswordExists, false), |
| 125 | + ]) |
| 126 | + let queryOperator: StytchB2BClient.Organizations.SearchParameters.SearchOperator = .AND |
| 127 | + await performSearch(operands: operands, operator: queryOperator, cursor: nil, limit: nil, label: "password exists false") |
| 128 | + } |
| 129 | + } |
| 130 | + |
| 131 | + private func searchByStatuses() { |
| 132 | + Task { |
| 133 | + let operands = makeOperands([ |
| 134 | + (.statuses, ["active", "invited"]), |
| 135 | + ]) |
| 136 | + let queryOperator: StytchB2BClient.Organizations.SearchParameters.SearchOperator = .OR |
| 137 | + await performSearch(operands: operands, operator: queryOperator, cursor: nil, limit: nil, label: "statuses") |
| 138 | + } |
| 139 | + } |
| 140 | + |
| 141 | + private func searchByRoles() { |
| 142 | + Task { |
| 143 | + let operands = makeOperands([ |
| 144 | + (.memberRoles, ["admin", "member"]), |
| 145 | + ]) |
| 146 | + let queryOperator: StytchB2BClient.Organizations.SearchParameters.SearchOperator = .OR |
| 147 | + await performSearch(operands: operands, operator: queryOperator, cursor: nil, limit: nil, label: "roles") |
| 148 | + } |
| 149 | + } |
| 150 | + |
| 151 | + private func searchAndEmailsAndPassword() { |
| 152 | + Task { |
| 153 | + let operands = makeOperands([ |
| 154 | + (.memberEmails, ["alpha@example.com"]), |
| 155 | + (.memberPasswordExists, true), |
| 156 | + ]) |
| 157 | + let queryOperator: StytchB2BClient.Organizations.SearchParameters.SearchOperator = .AND |
| 158 | + await performSearch(operands: operands, operator: queryOperator, cursor: nil, limit: nil, label: "AND emails and password") |
| 159 | + } |
| 160 | + } |
| 161 | + |
| 162 | + private func searchOrFuzzyEmailOrFuzzyPhone() { |
| 163 | + Task { |
| 164 | + let operands = makeOperands([ |
| 165 | + (.memberEmailFuzzy, "ali"), |
| 166 | + (.memberMfaPhoneNumberFuzzy, "401"), |
| 167 | + ]) |
| 168 | + let queryOperator: StytchB2BClient.Organizations.SearchParameters.SearchOperator = .OR |
| 169 | + await performSearch(operands: operands, operator: queryOperator, cursor: nil, limit: nil, label: "OR email fuzzy or phone fuzzy") |
| 170 | + } |
| 171 | + } |
| 172 | + |
| 173 | + private func searchWithPaginationSmallLimit() { |
| 174 | + Task { |
| 175 | + let operands = makeOperands([ |
| 176 | + (.memberEmails, ["alpha@example.com", "beta@example.com", "gamma@example.com"]), |
| 177 | + ]) |
| 178 | + let queryOperator: StytchB2BClient.Organizations.SearchParameters.SearchOperator = .OR |
| 179 | + await performSearch(operands: operands, operator: queryOperator, cursor: nil, limit: 2, label: "pagination with small limit") |
| 180 | + } |
| 181 | + } |
| 182 | + |
| 183 | + // MARK: - Common helpers |
| 184 | + |
| 185 | + private func performSearch( |
| 186 | + operands: [any SearchQueryOperand], |
| 187 | + operator searchOperator: StytchB2BClient.Organizations.SearchParameters.SearchOperator, |
| 188 | + cursor: String?, |
| 189 | + limit: Int?, |
| 190 | + label: String |
| 191 | + ) async { |
| 192 | + do { |
| 193 | + let query = StytchB2BClient.Organizations.SearchParameters.SearchQuery( |
| 194 | + searchOperator: searchOperator, |
| 195 | + searchOperands: operands |
| 196 | + ) |
| 197 | + let parameters = StytchB2BClient.Organizations.SearchParameters( |
| 198 | + query: query, |
| 199 | + cursor: cursor, |
| 200 | + limit: limit |
| 201 | + ) |
| 202 | + let response = try await StytchB2BClient.organizations.searchMembers(parameters: parameters) |
| 203 | + print("search \(label) members count: \(response.members.count)") |
| 204 | + if let nextCursor = response.resultsMetadata.nextCursor { |
| 205 | + print("search \(label) next_cursor: \(nextCursor)") |
| 206 | + } |
| 207 | + presentAlertAndLogMessage(description: "search \(label) success", object: response) |
| 208 | + } catch { |
| 209 | + presentAlertAndLogMessage(description: "search \(label) error", object: error) |
| 210 | + } |
| 211 | + } |
| 212 | + |
| 213 | + private func makeOperands(_ inputs: [(StytchB2BClient.Organizations.SearchParameters.SearchQueryOperandFilterNames, Any)]) -> [any SearchQueryOperand] { |
| 214 | + var builtOperands = [any SearchQueryOperand]() |
| 215 | + for input in inputs { |
| 216 | + if let operand = StytchB2BClient.Organizations.SearchParameters.searchQueryOperand( |
| 217 | + filterName: input.0, |
| 218 | + filterValue: input.1 |
| 219 | + ) { |
| 220 | + builtOperands.append(operand) |
| 221 | + } else { |
| 222 | + print("Invalid operand skipped for filter \(input.0.rawValue)") |
| 223 | + } |
| 224 | + } |
| 225 | + return builtOperands |
| 226 | + } |
| 227 | +} |
0 commit comments