diff --git a/Demo/Demo/Gravatar-Demo/DemoFetchProfileViewController.swift b/Demo/Demo/Gravatar-Demo/DemoFetchProfileViewController.swift index 44172e01..48c6123c 100644 --- a/Demo/Demo/Gravatar-Demo/DemoFetchProfileViewController.swift +++ b/Demo/Demo/Gravatar-Demo/DemoFetchProfileViewController.swift @@ -2,6 +2,12 @@ import UIKit import Gravatar class DemoFetchProfileViewController: UIViewController { + @StoredValue(keyName: "QEEmailKey", defaultValue: "") + var savedEmail: String + + @StoredValue(keyName: "QETokenKey", defaultValue: "") + var savedToken: String + let rootStackView: UIStackView = { let stack = UIStackView() stack.translatesAutoresizingMaskIntoConstraints = false @@ -19,8 +25,17 @@ class DemoFetchProfileViewController: UIViewController { control.selectedSegmentIndex = 0 return control }() - - let emailField: UITextField = { + + lazy var tokenField: UITextField = { + let textField = UITextField() + textField.translatesAutoresizingMaskIntoConstraints = false + textField.placeholder = "OAuth Token (Optional)" + textField.isSecureTextEntry = true + textField.text = savedToken + return textField + }() + + lazy var emailField: UITextField = { let textField = UITextField() textField.translatesAutoresizingMaskIntoConstraints = false textField.placeholder = "Email" @@ -28,6 +43,7 @@ class DemoFetchProfileViewController: UIViewController { textField.autocapitalizationType = .none textField.textContentType = .emailAddress textField.textAlignment = .center + textField.text = savedEmail return textField }() @@ -64,7 +80,7 @@ class DemoFetchProfileViewController: UIViewController { title = "Fetch Profile" view.backgroundColor = .systemBackground - for view in [segmentedControl, emailField, fetchProfileButton, activityIndicator, profileTextView] { + for view in [segmentedControl, tokenField, emailField, fetchProfileButton, activityIndicator, profileTextView] { rootStackView.addArrangedSubview(view) } view.addSubview(rootStackView) @@ -83,7 +99,14 @@ class DemoFetchProfileViewController: UIViewController { var identifier: ProfileIdentifier guard activityIndicator.isAnimating == false else { return } - + + if let tokenString = tokenField.text, tokenString.isEmpty == false { + Task { + await fetchOwnProfile(with: tokenString) + } + return + } + if segmentedControl.selectedSegmentIndex == 0 { guard let email = emailField.text, email.isEmpty == false else { return } identifier = .email(email) @@ -109,11 +132,23 @@ class DemoFetchProfileViewController: UIViewController { } } + func fetchOwnProfile(with token: String) async { + let service = ProfileService() + do { + let profile = try await service.fetchOwnProfile(token: token) + setProfile(with: profile) + } catch { + showError(error) + } + } + func setProfile(with profile: Profile) { activityIndicator.stopAnimating() profileTextView.text = """ Profile URL: \(profile.profileUrl) Display name: \(profile.displayName) +User login (authenticated): \(profile.userLogin ?? "nil") +User ID (authenticated): \(String(describing: profile.userId)) Preferred User Name: \(profile.displayName) Thumbnail URL: \(profile.avatarUrl) Verified accounts (\(profile.numberVerifiedAccounts ?? 0)): \(profile.verifiedAccounts) diff --git a/Sources/Gravatar/OpenApi/Generated/GetVerifiedAccountServices200Response.swift b/Sources/Gravatar/OpenApi/Generated/GetVerifiedAccountServices200Response.swift new file mode 100644 index 00000000..145fa6c6 --- /dev/null +++ b/Sources/Gravatar/OpenApi/Generated/GetVerifiedAccountServices200Response.swift @@ -0,0 +1,21 @@ +import Foundation + +struct GetVerifiedAccountServices200Response: Codable, Hashable, Sendable { + /// List of supported verified account services. + private(set) var services: [GetVerifiedAccountServices200ResponseServicesInner] + + init(services: [GetVerifiedAccountServices200ResponseServicesInner]) { + self.services = services + } + + enum CodingKeys: String, CodingKey, CaseIterable { + case services + } + + // Encodable protocol methods + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(services, forKey: .services) + } +} diff --git a/Sources/Gravatar/OpenApi/Generated/GetVerifiedAccountServices200ResponseServicesInner.swift b/Sources/Gravatar/OpenApi/Generated/GetVerifiedAccountServices200ResponseServicesInner.swift new file mode 100644 index 00000000..596c149d --- /dev/null +++ b/Sources/Gravatar/OpenApi/Generated/GetVerifiedAccountServices200ResponseServicesInner.swift @@ -0,0 +1,26 @@ +import Foundation + +struct GetVerifiedAccountServices200ResponseServicesInner: Codable, Hashable, Sendable { + /// The identifier for the service. + private(set) var id: String + /// The human-readable label for the service. + private(set) var label: String + + init(id: String, label: String) { + self.id = id + self.label = label + } + + enum CodingKeys: String, CodingKey, CaseIterable { + case id + case label + } + + // Encodable protocol methods + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(id, forKey: .id) + try container.encode(label, forKey: .label) + } +} diff --git a/Sources/Gravatar/OpenApi/Generated/Profile.swift b/Sources/Gravatar/OpenApi/Generated/Profile.swift index e618487f..e53b63dd 100644 --- a/Sources/Gravatar/OpenApi/Generated/Profile.swift +++ b/Sources/Gravatar/OpenApi/Generated/Profile.swift @@ -3,6 +3,10 @@ import Foundation /// A user's profile information. /// public struct Profile: Codable, Hashable, Sendable { + /// The unique user ID. NOTE: This is only provided in OAuth2 authenticated requests. + public private(set) var userId: Int? + /// The user's login name. NOTE: This is only provided in OAuth2 authenticated requests. + public private(set) var userLogin: String? /// The SHA256 hash of the user's primary email address. public private(set) var hash: String /// The user's display name. This is the name that is displayed on their profile. @@ -58,6 +62,8 @@ public struct Profile: Codable, Hashable, Sendable { public private(set) var registrationDate: Date? init( + userId: Int? = nil, + userLogin: String? = nil, hash: String, displayName: String, profileUrl: String, @@ -86,6 +92,8 @@ public struct Profile: Codable, Hashable, Sendable { lastProfileEdit: Date? = nil, registrationDate: Date? = nil ) { + self.userId = userId + self.userLogin = userLogin self.hash = hash self.displayName = displayName self.profileUrl = profileUrl @@ -116,6 +124,8 @@ public struct Profile: Codable, Hashable, Sendable { } enum CodingKeys: String, CodingKey, CaseIterable { + case userId = "user_id" + case userLogin = "user_login" case hash case displayName = "display_name" case profileUrl = "profile_url" @@ -149,6 +159,8 @@ public struct Profile: Codable, Hashable, Sendable { public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) + try container.encodeIfPresent(userId, forKey: .userId) + try container.encodeIfPresent(userLogin, forKey: .userLogin) try container.encode(hash, forKey: .hash) try container.encode(displayName, forKey: .displayName) try container.encode(profileUrl, forKey: .profileUrl) diff --git a/Sources/Gravatar/OpenApi/Generated/SearchProfilesByVerifiedAccount200Response.swift b/Sources/Gravatar/OpenApi/Generated/SearchProfilesByVerifiedAccount200Response.swift new file mode 100644 index 00000000..31881aa9 --- /dev/null +++ b/Sources/Gravatar/OpenApi/Generated/SearchProfilesByVerifiedAccount200Response.swift @@ -0,0 +1,25 @@ +import Foundation + +struct SearchProfilesByVerifiedAccount200Response: Codable, Hashable, Sendable { + private(set) var profiles: [Profile] + /// Total number of pages available. + private(set) var totalPages: Int + + init(profiles: [Profile], totalPages: Int) { + self.profiles = profiles + self.totalPages = totalPages + } + + enum CodingKeys: String, CodingKey, CaseIterable { + case profiles + case totalPages = "total_pages" + } + + // Encodable protocol methods + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(profiles, forKey: .profiles) + try container.encode(totalPages, forKey: .totalPages) + } +} diff --git a/Sources/Gravatar/OpenApi/Generated/UpdateProfileRequest.swift b/Sources/Gravatar/OpenApi/Generated/UpdateProfileRequest.swift index 3eeae140..f1f8e74c 100644 --- a/Sources/Gravatar/OpenApi/Generated/UpdateProfileRequest.swift +++ b/Sources/Gravatar/OpenApi/Generated/UpdateProfileRequest.swift @@ -21,6 +21,12 @@ public struct UpdateProfileRequest: Codable, Hashable, Sendable { public private(set) var jobTitle: String? /// The user's current company's name. public private(set) var company: String? + /// The user's cell phone number. + public private(set) var cellPhone: String? + /// The user's contact email address. + public private(set) var contactEmail: String? + /// Whether the user's contact information is hidden on their profile. + public private(set) var hiddenContactInfo: Bool? public init( firstName: String? = nil, @@ -31,7 +37,10 @@ public struct UpdateProfileRequest: Codable, Hashable, Sendable { pronouns: String? = nil, location: String? = nil, jobTitle: String? = nil, - company: String? = nil + company: String? = nil, + cellPhone: String? = nil, + contactEmail: String? = nil, + hiddenContactInfo: Bool? = nil ) { self.firstName = firstName self.lastName = lastName @@ -42,6 +51,9 @@ public struct UpdateProfileRequest: Codable, Hashable, Sendable { self.location = location self.jobTitle = jobTitle self.company = company + self.cellPhone = cellPhone + self.contactEmail = contactEmail + self.hiddenContactInfo = hiddenContactInfo } enum CodingKeys: String, CodingKey, CaseIterable { @@ -54,6 +66,9 @@ public struct UpdateProfileRequest: Codable, Hashable, Sendable { case location case jobTitle = "job_title" case company + case cellPhone = "cell_phone" + case contactEmail = "contact_email" + case hiddenContactInfo = "hidden_contact_info" } // Encodable protocol methods @@ -69,5 +84,8 @@ public struct UpdateProfileRequest: Codable, Hashable, Sendable { try container.encodeIfPresent(location, forKey: .location) try container.encodeIfPresent(jobTitle, forKey: .jobTitle) try container.encodeIfPresent(company, forKey: .company) + try container.encodeIfPresent(cellPhone, forKey: .cellPhone) + try container.encodeIfPresent(contactEmail, forKey: .contactEmail) + try container.encodeIfPresent(hiddenContactInfo, forKey: .hiddenContactInfo) } } diff --git a/access-control-modifier.swift b/access-control-modifier.swift index ee187927..94dee44b 100755 --- a/access-control-modifier.swift +++ b/access-control-modifier.swift @@ -8,7 +8,10 @@ let internalTypes: [String] = [ "AssociatedResponse", "Avatar", "AvatarRating", - "UpdateAvatarRequest" + "UpdateAvatarRequest", + "GetVerifiedAccountServices200Response", + "GetVerifiedAccountServices200ResponseServicesInner", + "SearchProfilesByVerifiedAccount200Response" ] let packageTypes: [String] = [ diff --git a/openapi/GravatarOpenAPIClient/.openapi-generator/FILES b/openapi/GravatarOpenAPIClient/.openapi-generator/FILES index ef1bebc5..3beec5b9 100644 --- a/openapi/GravatarOpenAPIClient/.openapi-generator/FILES +++ b/openapi/GravatarOpenAPIClient/.openapi-generator/FILES @@ -3,6 +3,8 @@ Sources/GravatarOpenAPIClient/Models/Avatar.swift Sources/GravatarOpenAPIClient/Models/AvatarRating.swift Sources/GravatarOpenAPIClient/Models/CryptoWalletAddress.swift Sources/GravatarOpenAPIClient/Models/GalleryImage.swift +Sources/GravatarOpenAPIClient/Models/GetVerifiedAccountServices200Response.swift +Sources/GravatarOpenAPIClient/Models/GetVerifiedAccountServices200ResponseServicesInner.swift Sources/GravatarOpenAPIClient/Models/Interest.swift Sources/GravatarOpenAPIClient/Models/Language.swift Sources/GravatarOpenAPIClient/Models/Link.swift @@ -10,6 +12,7 @@ Sources/GravatarOpenAPIClient/Models/ModelError.swift Sources/GravatarOpenAPIClient/Models/Profile.swift Sources/GravatarOpenAPIClient/Models/ProfileContactInfo.swift Sources/GravatarOpenAPIClient/Models/ProfilePayments.swift +Sources/GravatarOpenAPIClient/Models/SearchProfilesByVerifiedAccount200Response.swift Sources/GravatarOpenAPIClient/Models/SetEmailAvatarRequest.swift Sources/GravatarOpenAPIClient/Models/UpdateAvatarRequest.swift Sources/GravatarOpenAPIClient/Models/UpdateProfileRequest.swift diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index 7881ab97..ed23e3bf 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -251,6 +251,20 @@ components: - pronunciation - pronouns properties: + user_id: + type: integer + description: >- + The unique user ID. NOTE: This is only provided in OAuth2 + authenticated requests. + examples: + - 1234567890 + user_login: + type: string + description: >- + The user's login name. NOTE: This is only provided in OAuth2 + authenticated requests. + examples: + - alex hash: type: string description: The SHA256 hash of the user's primary email address. @@ -658,6 +672,146 @@ paths: $ref: '#/components/responses/rate_limit_exceeded' '500': description: Internal server error + /profiles/search/by-verified-account: + get: + summary: Search for profiles by verified account username + description: >- + Searches Gravatar profiles that have a verified account with the given + username. Optionally filter by the service (e.g., 'github', 'twitter'). + See `/verified-accounts/services` for supported ID values. Results are + paginated. + tags: + - experimental + operationId: searchProfilesByVerifiedAccount + parameters: + - name: username + in: query + required: true + description: The username used on the verified third-party service. + schema: + type: string + - name: service + in: query + required: false + description: >- + The service to limit search to (e.g., 'github', 'twitter'). See + `/verified-accounts/services` for supported IDs. + schema: + type: string + - name: page + in: query + required: false + description: Page number of results to retrieve. + schema: + type: integer + minimum: 1 + default: 1 + - name: per_page + in: query + required: false + description: 'Number of results per page. Defaults to 20, maximum 50.' + schema: + type: integer + minimum: 1 + maximum: 50 + default: 20 + security: + - apiKey: [] + responses: + '200': + description: List of matching profiles + content: + application/json: + schema: + type: object + required: + - profiles + - total_pages + properties: + profiles: + type: array + items: + $ref: '#/components/schemas/Profile' + total_pages: + type: integer + description: Total number of pages available. + examples: + - 5 + headers: + X-RateLimit-Limit: + $ref: '#/components/headers/X-RateLimit-Limit' + X-RateLimit-Remaining: + $ref: '#/components/headers/X-RateLimit-Remaining' + X-RateLimit-Reset: + $ref: '#/components/headers/X-RateLimit-Reset' + '400': + description: Missing or invalid parameters + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '401': + $ref: '#/components/responses/not_authorized' + '429': + $ref: '#/components/responses/rate_limit_exceeded' + '500': + description: Internal server error + /verified-accounts/services: + get: + summary: Get list of services supported as verified accounts + description: >- + Returns the list of verified account services with their ID and + human-readable labels. + tags: + - experimental + operationId: getVerifiedAccountServices + responses: + '200': + description: Supported verified account services + content: + application/json: + schema: + type: object + required: + - services + properties: + services: + type: array + description: List of supported verified account services. + items: + type: object + required: + - id + - label + properties: + id: + type: string + description: The identifier for the service. + examples: + - github + - twitter + - tumblr + - mastodon + label: + type: string + description: The human-readable label for the service. + examples: + - GitHub + - Twitter + - Tumblr + - Mastodon + examples: + - services: + - id: github + label: GitHub + - id: twitter + label: Twitter + - id: tumblr + label: Tumblr + - id: mastodon + label: Mastodon + '500': + description: Internal server error '/qr-code/{sha256_hash}': get: summary: Get QR code for an email address' profile @@ -839,6 +993,23 @@ paths: description: The user's current company's name. examples: - ACME Corp + cell_phone: + type: string + description: The user's cell phone number. + examples: + - '+1234567890' + contact_email: + type: string + description: The user's contact email address. + examples: + - alex@example.com + hidden_contact_info: + type: boolean + description: >- + Whether the user's contact information is hidden on their + profile. + examples: + - true responses: '200': description: Profile updated successfully