Skip to content

Commit

Permalink
feat(auth): add listUsers admin method (#539)
Browse files Browse the repository at this point in the history
  • Loading branch information
grdsdev authored Sep 24, 2024
1 parent 95e249f commit 1851262
Show file tree
Hide file tree
Showing 5 changed files with 1,385 additions and 11 deletions.
48 changes: 48 additions & 0 deletions Sources/Auth/AuthAdmin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,52 @@ public struct AuthAdmin: Sendable {
)
)
}

/// Get a list of users.
///
/// This function should only be called on a server.
///
/// - Warning: Never expose your `service_role` key in the client.
public func listUsers(params: PageParams? = nil) async throws -> ListUsersPaginatedResponse {
struct Response: Decodable {
let users: [User]
let aud: String
}

let httpResponse = try await api.execute(
HTTPRequest(
url: configuration.url.appendingPathComponent("admin/users"),
method: .get,
query: [
URLQueryItem(name: "page", value: params?.page?.description ?? ""),
URLQueryItem(name: "per_page", value: params?.perPage?.description ?? ""),
]
)
)

let response = try httpResponse.decoded(as: Response.self, decoder: configuration.decoder)

var pagination = ListUsersPaginatedResponse(
users: response.users,
aud: response.aud,
lastPage: 0,
total: httpResponse.headers["x-total-count"].flatMap(Int.init) ?? 0
)

let links = httpResponse.headers["link"]?.components(separatedBy: ",") ?? []
if !links.isEmpty {
for link in links {
let page = link.components(separatedBy: ";")[0].components(separatedBy: "=")[1].prefix(while: \.isNumber)
let rel = link.components(separatedBy: ";")[1].components(separatedBy: "=")[1]

if rel == "\"last\"", let lastPage = Int(page) {
pagination.lastPage = lastPage
} else if rel == "\"next\"", let nextPage = Int(page) {
pagination.nextPage = nextPage
}
}
}

return pagination
}
}
24 changes: 22 additions & 2 deletions Sources/Auth/Types.swift
Original file line number Diff line number Diff line change
Expand Up @@ -193,8 +193,8 @@ public struct User: Codable, Hashable, Identifiable, Sendable {
public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(UUID.self, forKey: .id)
appMetadata = try container.decode([String: AnyJSON].self, forKey: .appMetadata)
userMetadata = try container.decode([String: AnyJSON].self, forKey: .userMetadata)
appMetadata = try container.decodeIfPresent([String: AnyJSON].self, forKey: .appMetadata) ?? [:]
userMetadata = try container.decodeIfPresent([String: AnyJSON].self, forKey: .userMetadata) ?? [:]
aud = try container.decode(String.self, forKey: .aud)
confirmationSentAt = try container.decodeIfPresent(Date.self, forKey: .confirmationSentAt)
recoverySentAt = try container.decodeIfPresent(Date.self, forKey: .recoverySentAt)
Expand Down Expand Up @@ -816,3 +816,23 @@ public struct OAuthResponse: Codable, Hashable, Sendable {
public let provider: Provider
public let url: URL
}

public struct PageParams {
/// The page number.
public let page: Int?
/// Number of items returned per page.
public let perPage: Int?

public init(page: Int? = nil, perPage: Int? = nil) {
self.page = page
self.perPage = perPage
}
}

public struct ListUsersPaginatedResponse: Hashable, Sendable {
public let users: [User]
public let aud: String
public var nextPage: Int?
public var lastPage: Int
public var total: Int
}
58 changes: 52 additions & 6 deletions Tests/AuthTests/AuthClientTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,40 @@ final class AuthClientTests: XCTestCase {
XCTAssertEqual(receivedURL.value?.absoluteString, url)
}

func testAdminListUsers() async throws {
let sut = makeSUT { _ in
.stub(
fromFileName: "list-users-response",
headers: [
"X-Total-Count": "669",
"Link": "</admin/users?page=2&per_page=>; rel=\"next\", </admin/users?page=14&per_page=>; rel=\"last\"",
]
)
}

let response = try await sut.admin.listUsers()
XCTAssertEqual(response.total, 669)
XCTAssertEqual(response.nextPage, 2)
XCTAssertEqual(response.lastPage, 14)
}

func testAdminListUsers_noNextPage() async throws {
let sut = makeSUT { _ in
.stub(
fromFileName: "list-users-response",
headers: [
"X-Total-Count": "669",
"Link": "</admin/users?page=14&per_page=>; rel=\"last\"",
]
)
}

let response = try await sut.admin.listUsers()
XCTAssertEqual(response.total, 669)
XCTAssertNil(response.nextPage)
XCTAssertEqual(response.lastPage, 14)
}

private func makeSUT(
fetch: ((URLRequest) async throws -> HTTPResponse)? = nil
) -> AuthClient {
Expand All @@ -331,38 +365,50 @@ final class AuthClientTests: XCTestCase {
}

extension HTTPResponse {
static func stub(_ body: String = "", code: Int = 200) -> HTTPResponse {
static func stub(
_ body: String = "",
code: Int = 200,
headers: [String: String]? = nil
) -> HTTPResponse {
HTTPResponse(
data: body.data(using: .utf8)!,
response: HTTPURLResponse(
url: clientURL,
statusCode: code,
httpVersion: nil,
headerFields: nil
headerFields: headers
)!
)
}

static func stub(fromFileName fileName: String, code: Int = 200) -> HTTPResponse {
static func stub(
fromFileName fileName: String,
code: Int = 200,
headers: [String: String]? = nil
) -> HTTPResponse {
HTTPResponse(
data: json(named: fileName),
response: HTTPURLResponse(
url: clientURL,
statusCode: code,
httpVersion: nil,
headerFields: nil
headerFields: headers
)!
)
}

static func stub(_ value: some Encodable, code: Int = 200) -> HTTPResponse {
static func stub(
_ value: some Encodable,
code: Int = 200,
headers: [String: String]? = nil
) -> HTTPResponse {
HTTPResponse(
data: try! AuthClient.Configuration.jsonEncoder.encode(value),
response: HTTPURLResponse(
url: clientURL,
statusCode: code,
httpVersion: nil,
headerFields: nil
headerFields: headers
)!
)
}
Expand Down
Loading

0 comments on commit 1851262

Please sign in to comment.