stack/sdks/implementations/swift/Sources/StackAuth/Models/Team.swift
Konstantin Wohlwend 66b066db6e Swift SDK
2026-01-19 13:14:13 -08:00

211 lines
7.0 KiB
Swift

import Foundation
/// A team/organization that users can belong to
public actor Team {
private let client: APIClient
public nonisolated let id: String
public private(set) var displayName: String
public private(set) var profileImageUrl: String?
public private(set) var clientMetadata: [String: Any]
public private(set) var clientReadOnlyMetadata: [String: Any]
init(client: APIClient, json: [String: Any]) {
self.client = client
self.id = json["id"] as? String ?? ""
self.displayName = json["display_name"] as? String ?? ""
self.profileImageUrl = json["profile_image_url"] as? String
self.clientMetadata = json["client_metadata"] as? [String: Any] ?? [:]
self.clientReadOnlyMetadata = json["client_read_only_metadata"] as? [String: Any] ?? [:]
}
// MARK: - Update
public func update(
displayName: String? = nil,
profileImageUrl: String? = nil,
clientMetadata: [String: Any]? = nil
) async throws {
var body: [String: Any] = [:]
if let displayName = displayName { body["display_name"] = displayName }
if let profileImageUrl = profileImageUrl { body["profile_image_url"] = profileImageUrl }
if let clientMetadata = clientMetadata { body["client_metadata"] = clientMetadata }
let (data, _) = try await client.sendRequest(
path: "/teams/\(id)",
method: "PATCH",
body: body,
authenticated: true
)
if let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] {
self.displayName = json["display_name"] as? String ?? self.displayName
self.profileImageUrl = json["profile_image_url"] as? String
self.clientMetadata = json["client_metadata"] as? [String: Any] ?? self.clientMetadata
self.clientReadOnlyMetadata = json["client_read_only_metadata"] as? [String: Any] ?? self.clientReadOnlyMetadata
}
}
// MARK: - Delete
public func delete() async throws {
_ = try await client.sendRequest(
path: "/teams/\(id)",
method: "DELETE",
authenticated: true
)
}
// MARK: - Invite
public func inviteUser(email: String, callbackUrl: String? = nil) async throws {
var body: [String: Any] = [
"email": email,
"team_id": id
]
if let callbackUrl = callbackUrl {
body["callback_url"] = callbackUrl
}
_ = try await client.sendRequest(
path: "/team-invitations/send-code",
method: "POST",
body: body,
authenticated: true
)
}
// MARK: - List Users
public func listUsers() async throws -> [TeamUser] {
let (data, _) = try await client.sendRequest(
path: "/team-member-profiles?team_id=\(id)",
method: "GET",
authenticated: true
)
guard let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
let items = json["items"] as? [[String: Any]] else {
return []
}
return items.map { TeamUser(from: $0) }
}
// MARK: - Invitations
public func listInvitations() async throws -> [TeamInvitation] {
let (data, _) = try await client.sendRequest(
path: "/teams/\(id)/invitations",
method: "GET",
authenticated: true
)
guard let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
let items = json["items"] as? [[String: Any]] else {
return []
}
return items.map { TeamInvitation(client: client, teamId: id, json: $0) }
}
// MARK: - API Keys
public func listApiKeys() async throws -> [TeamApiKey] {
let (data, _) = try await client.sendRequest(
path: "/teams/\(id)/api-keys",
method: "GET",
authenticated: true
)
guard let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
let items = json["items"] as? [[String: Any]] else {
return []
}
return items.map { TeamApiKey(from: $0) }
}
public func createApiKey(
description: String,
expiresAt: Date? = nil,
scope: String? = nil
) async throws -> TeamApiKeyFirstView {
var body: [String: Any] = ["description": description]
if let expiresAt = expiresAt {
body["expires_at"] = Int64(expiresAt.timeIntervalSince1970 * 1000)
}
if let scope = scope { body["scope"] = scope }
let (data, _) = try await client.sendRequest(
path: "/teams/\(id)/api-keys",
method: "POST",
body: body,
authenticated: true
)
guard let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else {
throw StackAuthError(code: "parse_error", message: "Failed to parse API key response")
}
return TeamApiKeyFirstView(from: json)
}
}
// MARK: - Supporting Types
public struct TeamUser: Sendable {
public let id: String
public let teamProfile: TeamMemberProfile
init(from json: [String: Any]) {
// Try both "id" (from /users?team_id=) and "user_id" (from other endpoints)
self.id = json["id"] as? String ?? json["user_id"] as? String ?? ""
if let profile = json["team_profile"] as? [String: Any] {
self.teamProfile = TeamMemberProfile(
displayName: profile["display_name"] as? String,
profileImageUrl: profile["profile_image_url"] as? String
)
} else {
// If no team_profile, use display_name from user itself
self.teamProfile = TeamMemberProfile(
displayName: json["display_name"] as? String,
profileImageUrl: json["profile_image_url"] as? String
)
}
}
}
public struct TeamMemberProfile: Sendable {
public let displayName: String?
public let profileImageUrl: String?
}
public actor TeamInvitation {
private let client: APIClient
private let teamId: String
public nonisolated let id: String
public let recipientEmail: String?
public let expiresAt: Date
init(client: APIClient, teamId: String, json: [String: Any]) {
self.client = client
self.teamId = teamId
self.id = json["id"] as? String ?? ""
self.recipientEmail = json["recipient_email"] as? String
let millis = json["expires_at_millis"] as? Int64 ?? 0
self.expiresAt = Date(timeIntervalSince1970: Double(millis) / 1000.0)
}
public func revoke() async throws {
_ = try await client.sendRequest(
path: "/teams/\(teamId)/invitations/\(id)",
method: "DELETE",
authenticated: true
)
}
}