diff --git a/.buildkite/commands/build-demos.sh b/.buildkite/commands/build-demos.sh index fa538921..34f21fe9 100644 --- a/.buildkite/commands/build-demos.sh +++ b/.buildkite/commands/build-demos.sh @@ -3,6 +3,9 @@ echo "--- :rubygems: Setting up Gems" install_gems +echo "--- Generate Secrets.swift source file" +make secrets + echo "--- 🛠 Building Demo (Swift)" bundle exec fastlane build_demo scheme:Gravatar-Demo diff --git a/.gitignore b/.gitignore index a048c61f..1253557d 100644 --- a/.gitignore +++ b/.gitignore @@ -43,4 +43,7 @@ Carthage/Build fastlane/README.md fastlane/report.xml fastlane/test_output + +# Other openapi-generator/ +Demo/Demo/Gravatar-Demo/Secrets.swift diff --git a/Demo/Demo/Gravatar-Demo/AppDelegate.swift b/Demo/Demo/Gravatar-Demo/AppDelegate.swift index 44a9265b..79910f70 100644 --- a/Demo/Demo/Gravatar-Demo/AppDelegate.swift +++ b/Demo/Demo/Gravatar-Demo/AppDelegate.swift @@ -1,19 +1,15 @@ -// -// AppDelegate.swift -// Gravatar-Demo -// -// Created by Andrew Montgomery on 1/19/24. -// - import UIKit +import Gravatar @main class AppDelegate: UIResponder, UIApplicationDelegate { - - - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. + if let apiKey = apiKey { + Task { + await Configuration.shared.configure(with: apiKey) + } + } return true } @@ -30,7 +26,4 @@ class AppDelegate: UIResponder, UIApplicationDelegate { // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. // Use this method to release any resources that were specific to the discarded scenes, as they will not return. } - - } - diff --git a/Demo/Demo/Gravatar-Demo/DemoFetchProfileViewController.swift b/Demo/Demo/Gravatar-Demo/DemoFetchProfileViewController.swift index 22af61a6..daaaf729 100644 --- a/Demo/Demo/Gravatar-Demo/DemoFetchProfileViewController.swift +++ b/Demo/Demo/Gravatar-Demo/DemoFetchProfileViewController.swift @@ -112,10 +112,11 @@ class DemoFetchProfileViewController: UIViewController { profileTextView.text = """ Profile URL: \(profile.profileUrl) Display name: \(profile.displayName) -Name: \(profile.displayName) Preferred User Name: \(profile.displayName) Thumbnail URL: \(profile.avatarUrl) +Wallets: \(String(describing: profile.payments?.cryptoWallets)) Last edit: \(String(describing: profile.lastProfileEdit)) +Registration date: \(String(describing: profile.registrationDate)) """ } diff --git a/Demo/Gravatar-Demo.xcodeproj/project.pbxproj b/Demo/Gravatar-Demo.xcodeproj/project.pbxproj index d0c2ee5b..f31965fc 100644 --- a/Demo/Gravatar-Demo.xcodeproj/project.pbxproj +++ b/Demo/Gravatar-Demo.xcodeproj/project.pbxproj @@ -10,6 +10,7 @@ 1E0087932B63CFFE0012ECEA /* DemoFetchProfileViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E0087922B63CFFE0012ECEA /* DemoFetchProfileViewController.swift */; }; 1E0087952B63DBCB0012ECEA /* DemoUploadImageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E0087942B63DBCB0012ECEA /* DemoUploadImageViewController.swift */; }; 1ECAB5072BC984440043A331 /* DemoProfileConfigurationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ECAB5062BC984440043A331 /* DemoProfileConfigurationViewController.swift */; }; + 1ED769E72C048D9C00680D78 /* Secrets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED769E62C048D9C00680D78 /* Secrets.swift */; }; 4948C4EC2B61C41100AC4875 /* Gravatar in Frameworks */ = {isa = PBXBuildFile; productRef = 4948C4EB2B61C41100AC4875 /* Gravatar */; }; 4948C4EE2B61C41800AC4875 /* Gravatar in Frameworks */ = {isa = PBXBuildFile; productRef = 4948C4ED2B61C41800AC4875 /* Gravatar */; }; 495775E22B5B34970082812A /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 495775E12B5B34970082812A /* AppDelegate.swift */; }; @@ -37,6 +38,7 @@ 1E0087922B63CFFE0012ECEA /* DemoFetchProfileViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DemoFetchProfileViewController.swift; sourceTree = ""; }; 1E0087942B63DBCB0012ECEA /* DemoUploadImageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DemoUploadImageViewController.swift; sourceTree = ""; }; 1ECAB5062BC984440043A331 /* DemoProfileConfigurationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DemoProfileConfigurationViewController.swift; sourceTree = ""; }; + 1ED769E62C048D9C00680D78 /* Secrets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Secrets.swift; sourceTree = ""; }; 4948C4E92B61C3FD00AC4875 /* Gravatar-SDK-iOS */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = "Gravatar-SDK-iOS"; path = ..; sourceTree = ""; }; 495775DF2B5B34970082812A /* Gravatar-Demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Gravatar-Demo.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 495775E12B5B34970082812A /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; @@ -105,6 +107,7 @@ children = ( 91956A502B67939F00BF3CF0 /* Common */, 495775E12B5B34970082812A /* AppDelegate.swift */, + 1ED769E62C048D9C00680D78 /* Secrets.swift */, 91F0B3E12B6281A60025C4F8 /* Main.storyboard */, 495775E32B5B34970082812A /* SceneDelegate.swift */, 914AC0172BD7FF08005DA4A5 /* DemoBaseProfileViewController.swift */, @@ -288,6 +291,7 @@ 914AC01A2BD7FF08005DA4A5 /* DemoProfilePresentationStylesViewController.swift in Sources */, 91F0B3DE2B62815F0025C4F8 /* DemoAvatarDownloadViewController.swift in Sources */, 91956A542B67943A00BF3CF0 /* DemoUIImageViewExtensionViewController.swift in Sources */, + 1ED769E72C048D9C00680D78 /* Secrets.swift in Sources */, 914AC0202BDAAC3C005DA4A5 /* DemoRemoteSVGViewController.swift in Sources */, 495775E42B5B34970082812A /* SceneDelegate.swift in Sources */, 1ECAB5072BC984440043A331 /* DemoProfileConfigurationViewController.swift in Sources */, @@ -335,6 +339,7 @@ CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = PZYM8XX95Q; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = "Demo/Gravatar-Demo/Info.plist"; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; @@ -365,6 +370,7 @@ CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = PZYM8XX95Q; + ENABLE_USER_SCRIPT_SANDBOXING = NO; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = "Demo/Gravatar-Demo/Info.plist"; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; diff --git a/Makefile b/Makefile index 089aa4d4..88fca0c7 100644 --- a/Makefile +++ b/Makefile @@ -14,11 +14,13 @@ OPENAPI_GENERATOR_CLONE_DIR ?= $(CURRENT_MAKEFILE_DIR)/openapi-generator OPENAPI_YAML_PATH ?= $(CURRENT_MAKEFILE_DIR)/openapi/spec.yaml MODEL_TEMPLATE_PATH ?= $(CURRENT_MAKEFILE_DIR)/openapi OUTPUT_DIRECTORY ?= $(CURRENT_MAKEFILE_DIR)/Sources/Gravatar/OpenApi/Generated +SECRETS_PATH=$(CURRENT_MAKEFILE_DIR)/Demo/Demo/Gravatar-Demo/Secrets.swift # Derived values (don't change these). CURRENT_MAKEFILE_PATH := $(abspath $(lastword $(MAKEFILE_LIST))) CURRENT_MAKEFILE_DIR := $(patsubst %/,%,$(dir $(CURRENT_MAKEFILE_PATH))) + # If no target is specified, display help .DEFAULT_GOAL := help @@ -27,17 +29,17 @@ help: # Display this help. @-+echo @-+grep -Eh "^[a-z-]+:.*#" $(CURRENT_MAKEFILE_PATH) | sed -E 's/^(.*:)(.*#+)(.*)/ \1 @@@ \3 /' | column -t -s "@@@" -dev: # Open the package in xcode +dev: secrets # Open the package in xcode xed . -dev-demo: # Open an xcode project with the package and a demo project +dev-demo: secrets # Open an xcode project with the package and a demo project xed Demo/ test: bundle-install bundle exec fastlane test -build-demo: build-demo-swift build-demo-swiftui - +build-demo: secrets build-demo-swift build-demo-swiftui + build-demo-swift: bundle-install bundle exec fastlane build_demo scheme:Gravatar-Demo @@ -76,6 +78,12 @@ update-example-snapshots: cd ./Sources/GravatarUI/GravatarUI.docc/Resources/ProfileExamples && \ for filePath in *; do name=$${filePath%.*}; mv $$filePath $${name//-dark/~dark}@2x$${filePath#$$name}; done +secrets: # Creates the Secrets file in the Demo app. + if [ ! -f $(SECRETS_PATH) ]; then \ + touch $(SECRETS_PATH); \ + echo "let apiKey: String? = nil" > $(SECRETS_PATH); \ + fi + install-and-generate: $(OPENAPI_GENERATOR_CLONE_DIR) # Clones and setup the openapi-generator. "$(OPENAPI_GENERATOR_CLONE_DIR)"/run-in-docker.sh mvn package make generate diff --git a/Sources/Gravatar/Configuration.swift b/Sources/Gravatar/Configuration.swift new file mode 100644 index 00000000..0bc521a1 --- /dev/null +++ b/Sources/Gravatar/Configuration.swift @@ -0,0 +1,12 @@ +import Foundation + +public actor Configuration { + private(set) var apiKey: String? + public static let shared = Configuration() + + private init() {} + + public func configure(with apiKey: String?) { + self.apiKey = apiKey + } +} diff --git a/Sources/Gravatar/Network/Services/ProfileService.swift b/Sources/Gravatar/Network/Services/ProfileService.swift index 6d197ab2..44bc92f3 100644 --- a/Sources/Gravatar/Network/Services/ProfileService.swift +++ b/Sources/Gravatar/Network/Services/ProfileService.swift @@ -38,8 +38,7 @@ public struct ProfileService: ProfileFetching, Sendable { public func fetch(with profileID: ProfileIdentifier) async throws -> Profile { let url = baseURL.appending(pathComponent: profileID.id) - let request = URLRequest(url: url) - // TODO: Add API key to headers + let request = await URLRequest(url: url).authorized() return try await fetch(with: request) } } @@ -62,7 +61,9 @@ extension ProfileService { private func map(_ data: Data, _: HTTPURLResponse) -> Result { do { - let profile = try JSONDecoder().decode(Profile.self, from: data) + let decoder = JSONDecoder() + decoder.dateDecodingStrategy = .iso8601 + let profile = try decoder.decode(Profile.self, from: data) return .success(profile) } catch let error as HTTPClientError { return .failure(.responseError(reason: error.map())) @@ -75,3 +76,17 @@ extension ProfileService { } } } + +extension URLRequest { + private enum HeaderField: String { + case authorization = "Authorization" + } + + fileprivate func authorized() async -> URLRequest { + guard let key = await Configuration.shared.apiKey else { return self } + let bearerKey = "Bearer \(key)" + var copy = self + copy.setValue(bearerKey, forHTTPHeaderField: HeaderField.authorization.rawValue) + return copy + } +} diff --git a/Tests/GravatarTests/ProfileServiceTests.swift b/Tests/GravatarTests/ProfileServiceTests.swift new file mode 100644 index 00000000..f2f22823 --- /dev/null +++ b/Tests/GravatarTests/ProfileServiceTests.swift @@ -0,0 +1,54 @@ +@testable import Gravatar +import XCTest + +final class ProfileServiceTests: XCTestCase { + override func tearDown() async throws { + await Configuration.shared.configure(with: nil) + } + + func testProfileRequest() async { + guard let data = Bundle.fullProfileJsonData else { + return XCTFail("Could not create data") + } + let session = URLSessionMock(returnData: data, response: .successResponse()) + let service = ProfileService(client: HTTPClientMock(session: session)) + + do { + _ = try await service.fetch(with: .hashID("")) + XCTAssertNil(session.request?.value(forHTTPHeaderField: "Authorization")) + } catch { + XCTFail(error.localizedDescription) + } + } + + func testProfileRequestWithApiKey() async { + guard let data = Bundle.fullProfileJsonData else { + return XCTFail("Could not create data") + } + + await Configuration.shared.configure(with: "somekey") + + let session = URLSessionMock(returnData: data, response: .successResponse()) + let service = ProfileService(client: HTTPClientMock(session: session)) + + do { + _ = try await service.fetch(with: .hashID("")) + XCTAssertNotNil(session.request?.value(forHTTPHeaderField: "Authorization")) + } catch { + XCTFail(error.localizedDescription) + } + } +} + +extension Bundle { + func jsonData(forResource resource: String) -> Data? { + guard let url = Bundle.testsBundle.url(forResource: resource, withExtension: "json") else { + return nil + } + return try? Data(contentsOf: url) + } + + static var fullProfileJsonData: Data? { + testsBundle.jsonData(forResource: "fullProfile") + } +} diff --git a/Tests/GravatarTests/Resources/Profiles/FullProfileWithBoolsAsStrings.json b/Tests/GravatarTests/Resources/Profiles/FullProfileWithBoolsAsStrings.json deleted file mode 100644 index 29b1dca2..00000000 --- a/Tests/GravatarTests/Resources/Profiles/FullProfileWithBoolsAsStrings.json +++ /dev/null @@ -1,138 +0,0 @@ -{ - "entry": [ - { - "hash": "fake_hash", - "requestHash": "fake_requestHash", - "profileUrl": "https://fake_profileUrl.com", - "preferredUsername": "fake_preferredUsername", - "thumbnailUrl": "https://1.gravatar.com/avatar/ca38d22ece4e8f592db7cd75764e5a52", - "photos": [ - { - "value": "https://1.gravatar.com/avatar/ca38d22ece4e8f592db7cd75764e5a52", - "type": "thumbnail" - }, - { - "value": "https://1.gravatar.com/userimage/12108442/e453d707d347ff4e0b475f15d2df99ef" - } - ], - "last_profile_edit": "2023-12-01 20:25:10", - "hidden_avatar": false, - "hidden_contact_info": false, - "hidden_wallet": false, - "profileBackground": { - "color": "#4b5e72" - }, - "displayName": "fake_displayName", - "pronunciation": "fake_pronunciation", - "pronouns": "they/them/their", - "aboutMe": "fake biography", - "currentLocation": "fake location", - "phoneNumbers": [ - { - "type": "home", - "value": "555-1212" - }, - { - "type": "work", - "value": "867-5309" - }, - { - "type": "mobile", - "value": "555-1212" - } - ], - "contactInfo": [ - { - "type": "contactform", - "value": "https://contact.form" - }, - { - "type": "calendar", - "value": "https://calendar.com" - } - ], - "emails": [ - { - "primary": "true", - "value": "email@example.com" - } - ], - "accounts": [ - { - "domain": "fake_domain.com", - "display": "fake_domain.com", - "url": "https://fake_domain.com", - "iconUrl": "https://fake_domain.com/icon.jpg", - "username": "fake_username", - "verified": "true", - "name": "WordPress", - "shortname": "wordpress" - }, - { - "domain": "twitter.com", - "display": "@fakeuser", - "url": "https://twitter.com/noone", - "iconUrl": "https://secure.gravatar.com/icons/twitter-alt.svg", - "username": "fakeuser", - "verified": "true", - "name": "Twitter", - "shortname": "twitter" - }, - { - "domain": "mastodon.social", - "display": "@fakeuser@https://mastodon.social", - "url": "https://mastodon.social/@fakeuser", - "iconUrl": "https://secure.gravatar.com/icons/mastodonsocial.svg", - "username": "fakeuser", - "verified": "true", - "name": "Mastodon", - "shortname": "mastodonsocial" - } - ], - "currency": [ - { - "type": "bitcoin", - "value": "BTC" - }, - { - "type": "userdefcurrency_currency", - "value": "wallets" - } - ], - "payments": { - "paypalme": "https://fake_paypalme.com", - "patreon": "https://fake_patreon.com", - "payment": "https://fake_payment.com" - }, - "backups": { - "mastodonsocial": { - "status": "finished", - "message": "Backup finished: 2 contacts.", - "backedup_contacts": 2, - "timestamp": 1703754947, - "params": { - "user_id": 61108442, - "service_name": "mastodonsocial", - "types": [ - "followers", - "following" - ], - "identifier": "gravatar_backup_contacts_mastodonsocial", - "timestamp": 1703754947, - "service_user_identifier": "https://mastodon.social/@fakeuser" - }, - "download_token": "LDRrRk9vc0MwUkNsdDNMLy9IQVo5TituODRGV2Y5TFBXTUVkQTJVeFluV2RzOTFWTkhsZXErekhaUW1KNXJiMk5nME9XV2x1dkRpc1poSW5aRmVvbGoyTWtmT0VDVzJnbHlYZkZVdGN0aE09" - } - }, - "urls": [ - { - "value": "https://fake_url.com", - "title": "fake_title" - } - ], - "share_flags": { - "search_engines": false - } - } - ] -} diff --git a/Tests/GravatarTests/Resources/Profiles/FullProfileWithNativeBools.json b/Tests/GravatarTests/Resources/Profiles/FullProfileWithNativeBools.json deleted file mode 100644 index bf2e72f6..00000000 --- a/Tests/GravatarTests/Resources/Profiles/FullProfileWithNativeBools.json +++ /dev/null @@ -1,138 +0,0 @@ -{ - "entry": [ - { - "hash": "fake_hash", - "requestHash": "fake_requestHash", - "profileUrl": "https://fake_profileUrl.com", - "preferredUsername": "fake_preferredUsername", - "thumbnailUrl": "https://1.gravatar.com/avatar/ca38d22ece4e8f592db7cd75764e5a52", - "photos": [ - { - "value": "https://1.gravatar.com/avatar/ca38d22ece4e8f592db7cd75764e5a52", - "type": "thumbnail" - }, - { - "value": "https://1.gravatar.com/userimage/12108442/e453d707d347ff4e0b475f15d2df99ef" - } - ], - "last_profile_edit": "2023-12-01 20:25:10", - "hidden_avatar": false, - "hidden_contact_info": false, - "hidden_wallet": false, - "profileBackground": { - "color": "#4b5e72" - }, - "displayName": "fake_displayName", - "pronunciation": "fake_pronunciation", - "pronouns": "they/them/their", - "aboutMe": "fake biography", - "currentLocation": "fake location", - "phoneNumbers": [ - { - "type": "home", - "value": "555-1212" - }, - { - "type": "work", - "value": "867-5309" - }, - { - "type": "mobile", - "value": "555-1212" - } - ], - "contactInfo": [ - { - "type": "contactform", - "value": "https://contact.form" - }, - { - "type": "calendar", - "value": "https://calendar.com" - } - ], - "emails": [ - { - "primary": true, - "value": "email@example.com" - } - ], - "accounts": [ - { - "domain": "fake_domain.com", - "display": "fake_domain.com", - "url": "https://fake_domain.com", - "iconUrl": "https://fake_domain.com/icon.jpg", - "username": "fake_username", - "verified": true, - "name": "WordPress", - "shortname": "wordpress" - }, - { - "domain": "twitter.com", - "display": "@fakeuser", - "url": "https://twitter.com/noone", - "iconUrl": "https://secure.gravatar.com/icons/twitter-alt.svg", - "username": "fakeuser", - "verified": true, - "name": "Twitter", - "shortname": "twitter" - }, - { - "domain": "mastodon.social", - "display": "@fakeuser@https://mastodon.social", - "url": "https://mastodon.social/@fakeuser", - "iconUrl": "https://secure.gravatar.com/icons/mastodonsocial.svg", - "username": "fakeuser", - "verified": true, - "name": "Mastodon", - "shortname": "mastodonsocial" - } - ], - "currency": [ - { - "type": "bitcoin", - "value": "BTC" - }, - { - "type": "userdefcurrency_currency", - "value": "wallets" - } - ], - "payments": { - "paypalme": "https://fake_paypalme.com", - "patreon": "https://fake_patreon.com", - "payment": "https://fake_payment.com" - }, - "backups": { - "mastodonsocial": { - "status": "finished", - "message": "Backup finished: 2 contacts.", - "backedup_contacts": 2, - "timestamp": 1703754947, - "params": { - "user_id": 61108442, - "service_name": "mastodonsocial", - "types": [ - "followers", - "following" - ], - "identifier": "gravatar_backup_contacts_mastodonsocial", - "timestamp": 1703754947, - "service_user_identifier": "https://mastodon.social/@fakeuser" - }, - "download_token": "LDRrRk9vc0MwUkNsdDNMLy9IQVo5TituODRGV2Y5TFBXTUVkQTJVeFluV2RzOTFWTkhsZXErekhaUW1KNXJiMk5nME9XV2x1dkRpc1poSW5aRmVvbGoyTWtmT0VDVzJnbHlYZkZVdGN0aE09" - } - }, - "urls": [ - { - "value": "https://fake_url.com", - "title": "fake_title" - } - ], - "share_flags": { - "search_engines": false - } - } - ] -} diff --git a/Tests/GravatarTests/Resources/Profiles/NewlyCreatedProfile.json b/Tests/GravatarTests/Resources/Profiles/NewlyCreatedProfile.json deleted file mode 100644 index 06f32727..00000000 --- a/Tests/GravatarTests/Resources/Profiles/NewlyCreatedProfile.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "entry": [ - { - "hash": "fake_hash", - "requestHash": "fake_requestHash", - "profileUrl": "https://fake_profileUrl.com", - "preferredUsername": "fake_preferredUsername", - "thumbnailUrl": "https://1.gravatar.com/avatar/ca38d22ece4e8f592db7cd75764e5a52", - "photos": [ - { - "value": "https://1.gravatar.com/avatar/ca38d22ece4e8f592db7cd75764e5a52", - "type": "thumbnail" - } - ], - "displayName": "fake_displayName", - "urls": [ - ] - } - ] -} diff --git a/Tests/GravatarTests/Resources/Profiles/PartialProfile.json b/Tests/GravatarTests/Resources/Profiles/PartialProfile.json deleted file mode 100644 index 6def30c9..00000000 --- a/Tests/GravatarTests/Resources/Profiles/PartialProfile.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "entry": [ - { - "hash": "fake_hash", - "requestHash": "fake_requestHash", - "profileUrl": "https://fake_profileUrl.com", - "preferredUsername": "fake_preferredUsername", - "thumbnailUrl": "https://1.gravatar.com/avatar/ca38d22ece4e8f592db7cd75764e5a52", - "photos": [ - { - "value": "https://1.gravatar.com/avatar/ca38d22ece4e8f592db7cd75764e5a52", - "type": "thumbnail" - } - ], - "last_profile_edit": "2023-12-01 20:25:10", - "hidden_avatar": false, - "hidden_contact_info": "1", - "hidden_wallet": "1", - "displayName": "fake_displayName", - "backups": { - "mastodonsocial": { - "status": "finished", - "message": "Backup finished: 2 contacts.", - "backedup_contacts": 2, - "timestamp": 1703754947, - "params": { - "user_id": 61108442, - "service_name": "mastodonsocial", - "types": [ - "followers", - "following" - ], - "identifier": "gravatar_backup_contacts_mastodonsocial", - "timestamp": 1703754947, - "service_user_identifier": "https://mastodon.social/@fakeuser" - }, - "download_token": "LDRrRk9vc0MwUkNsdDNMLy9IQVo5TituODRGV2Y5TFBXTUVkQTJVeFluV2RzOTFWTkhsZXErekhaUW1KNXJiMk5nME9XV2x1dkRpc1poSW5aRmVvbGoyTWtmT0VDVzJnbHlYZkZVdGN0aE09" - } - }, - "urls": [ - ], - "share_flags": { - "search_engines": true - } - } - ] -} diff --git a/Tests/GravatarTests/Resources/Profiles/fullProfile.json b/Tests/GravatarTests/Resources/Profiles/fullProfile.json new file mode 100644 index 00000000..16b27fe7 --- /dev/null +++ b/Tests/GravatarTests/Resources/Profiles/fullProfile.json @@ -0,0 +1,60 @@ +{ + "hash": "somehash", + "display_name": "John Appleseed", + "profile_url": "https://gravatar.com/notreal", + "avatar_url": "https://2.gravatar.com/avatar/hashhash", + "avatar_alt_text": "", + "location": "Beach tennis court", + "description": "I'm a beach tennis player who enjoys the dynamic challenges and camaraderie of the game on the sand. Embracing the strategic aspects, beach tennis has become a fulfilling pastime for me, offering both excitement and a sense of community.\n\nBy Chat GPT.", + "job_title": "Engineer", + "company": "A company", + "verified_accounts": [ + { + "service_label": "WordPress", + "service_icon": "https://secure.gravatar.com/icons/wordpress.svg", + "url": "http://notreal.wordpress.com" + } + ], + "pronunciation": "John", + "pronouns": "he/him", + "links": [ + { + "label": "My site verified", + "url": "https://notreal.wordpress.com/" + }, + { + "label": "Gravatar", + "url": "https://gravatar.com" + } + ], + "payments": { + "links": [ + { + "label": "Patreon", + "url": "https://www.patreon.com/fakelink" + } + ], + "crypto_wallets": [ + { + "label": "Bitcoin", + "address": "someaddress" + } + ] + }, + "contact_info": { + "home_phone": "1800123-1234", + "work_phone": "", + "cell_phone": "", + "email": "thisisnotmy@email.com", + "contact_form": "https://example.com/contact", + "calendar": "https://calendar.google.com/yourlink" + }, + "gallery": [ + { + "url": "https://1.gravatar.com/userimage/133992229/hashhash" + } + ], + "number_verified_accounts": 1, + "last_profile_edit": "2024-05-28T14:17:03Z", + "registration_date": "2018-01-24T14:23:32Z" +} diff --git a/openapi/spec.yaml b/openapi/spec.yaml index 324b461a..7a970053 100644 --- a/openapi/spec.yaml +++ b/openapi/spec.yaml @@ -269,9 +269,16 @@ components: description: >- The date the user registered their account. This is only provided in authenticated API requests. - format: date + format: date-time examples: - - '2021-10-01' + - '2021-10-01T12:00:00Z' + securitySchemes: + apiKey: + type: http + scheme: bearer + description: >- + Bearer token to authenticate the request. Full profile information is + only available in authenticated requests. paths: /profiles/{profileIdentifier}: get: @@ -289,14 +296,6 @@ paths: slug. schema: type: string - - name: Authorization - in: header - required: false - schema: - type: string - description: >- - Bearer token to authenticate the request. Full profile information - is only available in authenticated requests. responses: '200': description: Successful response @@ -310,3 +309,5 @@ paths: description: Rate limit exceeded '500': description: Internal server error +security: + - apiKey: []