Merge pull request #49 from tootsuite/feature/setup-avatar

Update avatar and display name after sign-up flow
This commit is contained in:
CMK 2021-03-09 16:35:21 +08:00 committed by GitHub
commit 00cf194f0b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 438 additions and 213 deletions

View File

@ -7,7 +7,7 @@
<key>CoreDataStack.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>7</integer>
<integer>10</integer>
</dict>
<key>Mastodon - RTL.xcscheme_^#shared#^_</key>
<dict>
@ -22,7 +22,7 @@
<key>Mastodon.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>8</integer>
<integer>7</integer>
</dict>
</dict>
<key>SuppressBuildableAutocreation</key>

View File

@ -111,7 +111,24 @@ extension MastodonConfirmEmailViewController {
case .failure(let error):
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: swap user access token swap fail: %s", (#file as NSString).lastPathComponent, #line, #function, error.localizedDescription)
case .finished:
break
// upload avatar and set display name in the background
self.context.apiService.accountUpdateCredentials(
domain: self.viewModel.authenticateInfo.domain,
query: self.viewModel.updateCredentialQuery,
authorization: Mastodon.API.OAuth.Authorization(accessToken: self.viewModel.userToken.accessToken)
)
.retry(3)
.sink { completion in
switch completion {
case .failure(let error):
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: setup avatar & display name fail: %s", ((#file as NSString).lastPathComponent), #line, #function, error.localizedDescription)
case .finished:
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: setup avatar & display name success", ((#file as NSString).lastPathComponent), #line, #function)
}
} receiveValue: { _ in
// do nothing
}
.store(in: &self.context.disposeBag) // execute in the background
}
} receiveValue: { response in
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: user %s's email confirmed", ((#file as NSString).lastPathComponent), #line, #function, response.value.username)

View File

@ -12,20 +12,29 @@ import MastodonSDK
final class MastodonConfirmEmailViewModel {
var disposeBag = Set<AnyCancellable>()
// input
let context: AppContext
var email: String
let authenticateInfo: AuthenticationViewModel.AuthenticateInfo
let userToken: Mastodon.Entity.Token
let updateCredentialQuery: Mastodon.API.Account.UpdateCredentialQuery
let timestampUpdatePublisher = Timer.publish(every: 4.0, on: .main, in: .common)
.autoconnect()
.share()
.eraseToAnyPublisher()
init(context: AppContext, email: String, authenticateInfo: AuthenticationViewModel.AuthenticateInfo, userToken: Mastodon.Entity.Token) {
init(
context: AppContext,
email: String,
authenticateInfo: AuthenticationViewModel.AuthenticateInfo,
userToken: Mastodon.Entity.Token,
updateCredentialQuery: Mastodon.API.Account.UpdateCredentialQuery
) {
self.context = context
self.email = email
self.authenticateInfo = authenticateInfo
self.userToken = userToken
self.updateCredentialQuery = updateCredentialQuery
}
}

View File

@ -5,12 +5,12 @@
// Created by MainasuK Cirno on 2021-2-5.
//
import AlamofireImage
import Combine
import MastodonSDK
import os.log
import PhotosUI
import UIKit
import UITextField_Shake
final class MastodonRegisterViewController: UIViewController, NeedsDependency, OnboardingViewControllerAppearance {
var disposeBag = Set<AnyCancellable>()
@ -623,10 +623,10 @@ extension MastodonRegisterViewController {
username: username,
email: email,
password: password,
agreement: true, // TODO:
locale: "en" // TODO:
agreement: true, // user confirmed in the server rules scene
locale: Locale.current.languageCode ?? "en"
)
// register without show server rules
context.apiService.accountRegister(
domain: viewModel.domain,
@ -646,7 +646,21 @@ extension MastodonRegisterViewController {
} receiveValue: { [weak self] response in
guard let self = self else { return }
let userToken = response.value
let viewModel = MastodonConfirmEmailViewModel(context: self.context, email: email, authenticateInfo: self.viewModel.authenticateInfo, userToken: userToken)
let updateCredentialQuery: Mastodon.API.Account.UpdateCredentialQuery = {
let displayName: String? = self.viewModel.displayName.value.isEmpty ? nil : self.viewModel.displayName.value
let avatar: Mastodon.Query.MediaAttachment? = {
guard let avatarImage = self.viewModel.avatarImage.value else { return nil }
guard avatarImage.size.width <= 400 else {
return .jpeg(avatarImage.af.imageScaled(to: CGSize(width: 400, height: 400)).jpegData(compressionQuality: 0.8))
}
return .jpeg(avatarImage.jpegData(compressionQuality: 0.8))
}()
return Mastodon.API.Account.UpdateCredentialQuery(
displayName: displayName,
avatar: avatar
)
}()
let viewModel = MastodonConfirmEmailViewModel(context: self.context, email: email, authenticateInfo: self.viewModel.authenticateInfo, userToken: userToken, updateCredentialQuery: updateCredentialQuery)
self.coordinator.present(scene: .mastodonConfirmEmail(viewModel: viewModel), from: self, transition: .show)
}
.store(in: &disposeBag)

View File

@ -43,6 +43,39 @@ extension APIService {
.eraseToAnyPublisher()
}
func accountUpdateCredentials(
domain: String,
query: Mastodon.API.Account.UpdateCredentialQuery,
authorization: Mastodon.API.OAuth.Authorization
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Account>, Error> {
return Mastodon.API.Account.updateCredentials(
session: session,
domain: domain,
query: query,
authorization: authorization
)
.flatMap { response -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Account>, Error> in
let log = OSLog.api
let account = response.value
return self.backgroundManagedObjectContext.performChanges {
let (mastodonUser, isCreated) = APIService.CoreData.createOrMergeMastodonUser(
into: self.backgroundManagedObjectContext,
for: nil,
in: domain,
entity: account,
networkDate: response.networkDate,
log: log)
let flag = isCreated ? "+" : "-"
os_log(.info, log: log, "%{public}s[%{public}ld], %{public}s: mastodon user [%s](%s)%s verifed", ((#file as NSString).lastPathComponent), #line, #function, flag, mastodonUser.id, mastodonUser.username)
}
.setFailureType(to: Error.self)
.map { _ in return response }
.eraseToAnyPublisher()
}
.eraseToAnyPublisher()
}
func accountRegister(
domain: String,
query: Mastodon.API.Account.RegisterQuery,

View File

@ -0,0 +1,227 @@
//
// Mastodon+API+Account+Credentials.swift
//
//
// Created by MainasuK Cirno on 2021-3-8.
//
import Foundation
import Combine
// MARK: - Account credentials
extension Mastodon.API.Account {
static func accountsEndpointURL(domain: String) -> URL {
return Mastodon.API.endpointURL(domain: domain).appendingPathComponent("accounts")
}
/// Register an account
///
/// Creates a user and account records.
///
/// - Since: 2.7.0
/// - Version: 3.3.0
/// # Last Update
/// 2021/2/9
/// # Reference
/// [Document](https://docs.joinmastodon.org/methods/accounts/)
/// - Parameters:
/// - session: `URLSession`
/// - domain: Mastodon instance domain. e.g. "example.com"
/// - query: `RegisterQuery` with account registration information
/// - authorization: App token
/// - Returns: `AnyPublisher` contains `Token` nested in the response
public static func register(
session: URLSession,
domain: String,
query: RegisterQuery,
authorization: Mastodon.API.OAuth.Authorization
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Token>, Error> {
let request = Mastodon.API.post(
url: accountsEndpointURL(domain: domain),
query: query,
authorization: authorization
)
return session.dataTaskPublisher(for: request)
.tryMap { data, response in
let value = try Mastodon.API.decode(type: Mastodon.Entity.Token.self, from: data, response: response)
return Mastodon.Response.Content(value: value, response: response)
}
.eraseToAnyPublisher()
}
public struct RegisterQuery: Codable, PostQuery {
public let reason: String?
public let username: String
public let email: String
public let password: String
public let agreement: Bool
public let locale: String
public init(reason: String? = nil, username: String, email: String, password: String, agreement: Bool, locale: String) {
self.reason = reason
self.username = username
self.email = email
self.password = password
self.agreement = agreement
self.locale = locale
}
}
}
extension Mastodon.API.Account {
static func verifyCredentialsEndpointURL(domain: String) -> URL {
return Mastodon.API.endpointURL(domain: domain).appendingPathComponent("accounts/verify_credentials")
}
/// Verify account credentials
///
/// Test to make sure that the user token works.
///
/// - Since: 0.0.0
/// - Version: 3.3.0
/// # Last Update
/// 2021/2/9
/// # Reference
/// [Document](https://docs.joinmastodon.org/methods/accounts/)
/// - Parameters:
/// - session: `URLSession`
/// - domain: Mastodon instance domain. e.g. "example.com"
/// - authorization: App token
/// - Returns: `AnyPublisher` contains `Account` nested in the response
public static func verifyCredentials(
session: URLSession,
domain: String,
authorization: Mastodon.API.OAuth.Authorization
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Account>, Error> {
let request = Mastodon.API.get(
url: verifyCredentialsEndpointURL(domain: domain),
query: nil,
authorization: authorization
)
return session.dataTaskPublisher(for: request)
.tryMap { data, response in
let value = try Mastodon.API.decode(type: Mastodon.Entity.Account.self, from: data, response: response)
return Mastodon.Response.Content(value: value, response: response)
}
.eraseToAnyPublisher()
}
static func updateCredentialsEndpointURL(domain: String) -> URL {
return Mastodon.API.endpointURL(domain: domain).appendingPathComponent("accounts/update_credentials")
}
/// Update account credentials
///
/// Update the user's display and preferences.
///
/// - Since: 1.1.1
/// - Version: 3.3.0
/// # Last Update
/// 2021/2/9
/// # Reference
/// [Document](https://docs.joinmastodon.org/methods/accounts/)
/// - Parameters:
/// - session: `URLSession`
/// - domain: Mastodon instance domain. e.g. "example.com"
/// - query: `CredentialQuery` with update credential information
/// - authorization: user token
/// - Returns: `AnyPublisher` contains updated `Account` nested in the response
public static func updateCredentials(
session: URLSession,
domain: String,
query: UpdateCredentialQuery,
authorization: Mastodon.API.OAuth.Authorization
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Account>, Error> {
let request = Mastodon.API.patch(
url: updateCredentialsEndpointURL(domain: domain),
query: query,
authorization: authorization
)
return session.dataTaskPublisher(for: request)
.tryMap { data, response in
let value = try Mastodon.API.decode(type: Mastodon.Entity.Account.self, from: data, response: response)
return Mastodon.Response.Content(value: value, response: response)
}
.eraseToAnyPublisher()
}
public struct UpdateCredentialQuery: PatchQuery {
public let discoverable: Bool?
public let bot: Bool?
public let displayName: String?
public let note: String?
public let avatar: Mastodon.Query.MediaAttachment?
public let header: Mastodon.Query.MediaAttachment?
public let locked: Bool?
public let source: Mastodon.Entity.Source?
public let fieldsAttributes: [Mastodon.Entity.Field]?
enum CodingKeys: String, CodingKey {
case discoverable
case bot
case displayName = "display_name"
case note
case avatar
case header
case locked
case source
case fieldsAttributes = "fields_attributes"
}
public init(
discoverable: Bool? = nil,
bot: Bool? = nil,
displayName: String? = nil,
note: String? = nil,
avatar: Mastodon.Query.MediaAttachment? = nil,
header: Mastodon.Query.MediaAttachment? = nil,
locked: Bool? = nil,
source: Mastodon.Entity.Source? = nil,
fieldsAttributes: [Mastodon.Entity.Field]? = nil
) {
self.discoverable = discoverable
self.bot = bot
self.displayName = displayName
self.note = note
self.avatar = avatar
self.header = header
self.locked = locked
self.source = source
self.fieldsAttributes = fieldsAttributes
}
var contentType: String? {
return Self.multipartContentType()
}
var body: Data? {
var data = Data()
discoverable.flatMap { data.append(Data.multipart(key: "discoverable", value: $0)) }
bot.flatMap { data.append(Data.multipart(key: "bot", value: $0)) }
displayName.flatMap { data.append(Data.multipart(key: "display_name", value: $0)) }
note.flatMap { data.append(Data.multipart(key: "note", value: $0)) }
avatar.flatMap { data.append(Data.multipart(key: "avatar", value: $0)) }
header.flatMap { data.append(Data.multipart(key: "header", value: $0)) }
locked.flatMap { data.append(Data.multipart(key: "locked", value: $0)) }
if let source = source {
source.privacy.flatMap { data.append(Data.multipart(key: "source[privacy]", value: $0.rawValue)) }
source.sensitive.flatMap { data.append(Data.multipart(key: "source[privacy]", value: $0)) }
source.language.flatMap { data.append(Data.multipart(key: "source[privacy]", value: $0)) }
}
fieldsAttributes.flatMap { fieldsAttributes in
for fieldsAttribute in fieldsAttributes {
data.append(Data.multipart(key: "fields_attributes[name][]", value: fieldsAttribute.name))
data.append(Data.multipart(key: "fields_attributes[value][]", value: fieldsAttribute.value))
}
}
data.append(Data.multipartEnd())
return data
}
}
}

View File

@ -8,119 +8,17 @@
import Foundation
import Combine
// MARK: - Retrieve information
extension Mastodon.API.Account {
static func verifyCredentialsEndpointURL(domain: String) -> URL {
return Mastodon.API.endpointURL(domain: domain).appendingPathComponent("accounts/verify_credentials")
}
static func accountsEndpointURL(domain: String) -> URL {
return Mastodon.API.endpointURL(domain: domain).appendingPathComponent("accounts")
}
static func accountsInfoEndpointURL(domain: String, id: String) -> URL {
return Mastodon.API.endpointURL(domain: domain).appendingPathComponent("accounts")
return Mastodon.API.endpointURL(domain: domain)
.appendingPathComponent("accounts")
.appendingPathComponent(id)
}
static func updateCredentialsEndpointURL(domain: String) -> URL {
return Mastodon.API.endpointURL(domain: domain).appendingPathComponent("accounts/update_credentials")
}
/// Test to make sure that the user token works.
/// Retrieve information
///
/// - Since: 0.0.0
/// - Version: 3.3.0
/// # Last Update
/// 2021/2/9
/// # Reference
/// [Document](https://docs.joinmastodon.org/methods/accounts/)
/// - Parameters:
/// - session: `URLSession`
/// - domain: Mastodon instance domain. e.g. "example.com"
/// - authorization: App token
/// - Returns: `AnyPublisher` contains `Account` nested in the response
public static func verifyCredentials(
session: URLSession,
domain: String,
authorization: Mastodon.API.OAuth.Authorization
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Account>, Error> {
let request = Mastodon.API.get(
url: verifyCredentialsEndpointURL(domain: domain),
query: nil,
authorization: authorization
)
return session.dataTaskPublisher(for: request)
.tryMap { data, response in
let value = try Mastodon.API.decode(type: Mastodon.Entity.Account.self, from: data, response: response)
return Mastodon.Response.Content(value: value, response: response)
}
.eraseToAnyPublisher()
}
/// Creates a user and account records.
///
/// - Since: 2.7.0
/// - Version: 3.3.0
/// # Last Update
/// 2021/2/9
/// # Reference
/// [Document](https://docs.joinmastodon.org/methods/accounts/)
/// - Parameters:
/// - session: `URLSession`
/// - domain: Mastodon instance domain. e.g. "example.com"
/// - query: `RegisterQuery` with account registration information
/// - authorization: App token
/// - Returns: `AnyPublisher` contains `Token` nested in the response
public static func register(
session: URLSession,
domain: String,
query: RegisterQuery,
authorization: Mastodon.API.OAuth.Authorization
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Token>, Error> {
let request = Mastodon.API.post(
url: accountsEndpointURL(domain: domain),
query: query,
authorization: authorization
)
return session.dataTaskPublisher(for: request)
.tryMap { data, response in
let value = try Mastodon.API.decode(type: Mastodon.Entity.Token.self, from: data, response: response)
return Mastodon.Response.Content(value: value, response: response)
}
.eraseToAnyPublisher()
}
/// Update the user's display and preferences.
///
/// - Since: 1.1.1
/// - Version: 3.3.0
/// # Last Update
/// 2021/2/9
/// # Reference
/// [Document](https://docs.joinmastodon.org/methods/accounts/)
/// - Parameters:
/// - session: `URLSession`
/// - domain: Mastodon instance domain. e.g. "example.com"
/// - query: `CredentialQuery` with update credential information
/// - authorization: user token
/// - Returns: `AnyPublisher` contains updated `Account` nested in the response
public static func updateCredentials(
session: URLSession,
domain: String,
query: UpdateCredentialQuery,
authorization: Mastodon.API.OAuth.Authorization
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Account>, Error> {
let request = Mastodon.API.patch(
url: updateCredentialsEndpointURL(domain: domain),
query: query,
authorization: authorization
)
return session.dataTaskPublisher(for: request)
.tryMap { data, response in
let value = try Mastodon.API.decode(type: Mastodon.Entity.Account.self, from: data, response: response)
return Mastodon.Response.Content(value: value, response: response)
}
.eraseToAnyPublisher()
}
/// View information about a profile.
///
/// - Since: 0.0.0
@ -138,11 +36,11 @@ extension Mastodon.API.Account {
public static func accountInfo(
session: URLSession,
domain: String,
query: AccountInfoQuery,
userID: Mastodon.Entity.Account.ID,
authorization: Mastodon.API.OAuth.Authorization?
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Account>, Error> {
let request = Mastodon.API.get(
url: accountsInfoEndpointURL(domain: domain, id: query.id),
url: accountsInfoEndpointURL(domain: domain, id: userID),
query: nil,
authorization: authorization
)
@ -155,79 +53,3 @@ extension Mastodon.API.Account {
}
}
extension Mastodon.API.Account {
public struct RegisterQuery: Codable, PostQuery {
public let reason: String?
public let username: String
public let email: String
public let password: String
public let agreement: Bool
public let locale: String
public init(reason: String? = nil, username: String, email: String, password: String, agreement: Bool, locale: String) {
self.reason = reason
self.username = username
self.email = email
self.password = password
self.agreement = agreement
self.locale = locale
}
}
public struct UpdateCredentialQuery: Codable, PatchQuery {
public var discoverable: Bool?
public var bot: Bool?
public var displayName: String?
public var note: String?
public var avatar: String?
public var header: String?
public var locked: Bool?
public var source: Mastodon.Entity.Source?
public var fieldsAttributes: [Mastodon.Entity.Field]?
enum CodingKeys: String, CodingKey {
case discoverable
case bot
case displayName = "display_name"
case note
case avatar
case header
case locked
case source
case fieldsAttributes = "fields_attributes"
}
public init(
discoverable: Bool? = nil,
bot: Bool? = nil,
displayName: String? = nil,
note: String? = nil,
avatar: Mastodon.Entity.MediaAttachment? = nil,
header: Mastodon.Entity.MediaAttachment? = nil,
locked: Bool? = nil,
source: Mastodon.Entity.Source? = nil,
fieldsAttributes: [Mastodon.Entity.Field]? = nil
) {
self.discoverable = discoverable
self.bot = bot
self.displayName = displayName
self.note = note
self.avatar = avatar?.base64EncondedString
self.header = header?.base64EncondedString
self.locked = locked
self.source = source
self.fieldsAttributes = fieldsAttributes
}
}
public struct AccountInfoQuery: GetQuery {
public let id: String
var queryItems: [URLQueryItem]? { nil }
}
}

View File

@ -140,8 +140,13 @@ extension Mastodon.API {
timeoutInterval: Mastodon.API.timeoutInterval
)
request.httpMethod = method.rawValue
request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type")
request.httpBody = query?.body
if let contentType = query?.contentType {
request.setValue(contentType, forHTTPHeaderField: "Content-Type")
}
if let body = query?.body {
request.httpBody = body
request.setValue("\(body.count)", forHTTPHeaderField: "Content-Length")
}
if let authorization = authorization {
request.setValue(
"Bearer \(authorization.accessToken)",

View File

@ -0,0 +1,37 @@
//
// Data.swift
//
//
// Created by MainasuK Cirno on 2021-3-8.
//
import Foundation
extension Data {
static func multipart(
boundary: String = Multipart.boundary,
key: String,
value: MultipartFormValue
) -> Data {
var data = Data()
data.append("\r\n--\(boundary)\r\n".data(using: .utf8)!)
data.append("Content-Disposition: form-data; name=\"\(key)\"".data(using: .utf8)!)
if let filename = value.multipartFilename {
data.append("; filename=\"\(filename)\"\r\n".data(using: .utf8)!)
} else {
data.append("\r\n".data(using: .utf8)!)
}
if let contentType = value.multipartContentType {
data.append("Content-Type: \(contentType)\r\n".data(using: .utf8)!)
}
data.append("\r\n".data(using: .utf8)!)
data.append(value.multipartValue)
return data
}
static func multipartEnd(boundary: String = Multipart.boundary) -> Data {
return "\r\n--\(boundary)--\r\n".data(using: .utf8)!
}
}

View File

@ -12,4 +12,5 @@ public enum Mastodon {
public enum Response { }
public enum API { }
public enum Entity { }
public enum Query { }
}

View File

@ -1,5 +1,5 @@
//
// Mastodon+Entity+MediaAttachment.swift
// MediaAttachment.swift
//
//
// Created by jk234ert on 2/9/21.
@ -7,7 +7,7 @@
import Foundation
extension Mastodon.Entity {
extension Mastodon.Query {
public enum MediaAttachment {
/// JPEG (Joint Photographic Experts Group) image
case jpeg(Data?)
@ -20,7 +20,7 @@ extension Mastodon.Entity {
}
}
extension Mastodon.Entity.MediaAttachment {
extension Mastodon.Query.MediaAttachment {
var data: Data? {
switch self {
case .jpeg(let data): return data
@ -31,11 +31,12 @@ extension Mastodon.Entity.MediaAttachment {
}
var fileName: String {
let name = UUID().uuidString
switch self {
case .jpeg: return "file.jpg"
case .gif: return "file.gif"
case .png: return "file.png"
case .other(_, let fileExtension, _): return "file.\(fileExtension)"
case .jpeg: return "\(name).jpg"
case .gif: return "\(name).gif"
case .png: return "\(name).png"
case .other(_, let fileExtension, _): return "\(name).\(fileExtension)"
}
}
@ -53,3 +54,8 @@ extension Mastodon.Entity.MediaAttachment {
}
}
extension Mastodon.Query.MediaAttachment: MultipartFormValue {
var multipartValue: Data { return data ?? Data() }
var multipartContentType: String? { return mimeType }
var multipartFilename: String? { return fileName }
}

View File

@ -0,0 +1,37 @@
//
// MultipartFormValue.swift
//
//
// Created by MainasuK Cirno on 2021-3-8.
//
import Foundation
enum Multipart {
static let boundary = "__boundary__"
}
protocol MultipartFormValue {
var multipartValue: Data { get }
var multipartContentType: String? { get }
var multipartFilename: String? { get }
}
extension Bool: MultipartFormValue {
var multipartValue: Data {
switch self {
case true: return "true".data(using: .utf8)!
case false: return "false".data(using: .utf8)!
}
}
var multipartContentType: String? { return nil }
var multipartFilename: String? { return nil }
}
extension String: MultipartFormValue {
var multipartValue: Data {
return self.data(using: .utf8)!
}
var multipartContentType: String? { return nil }
var multipartFilename: String? { return nil }
}

View File

@ -14,12 +14,22 @@ enum RequestMethod: String {
protocol RequestQuery {
// All kinds of queries could have queryItems and body
var queryItems: [URLQueryItem]? { get }
var contentType: String? { get }
var body: Data? { get }
}
extension RequestQuery {
static func multipartContentType(boundary: String = Multipart.boundary) -> String {
return "multipart/form-data; charset=utf-8; boundary=\"\(boundary)\""
}
}
// An `Encodable` query provides its body by encoding itself
// A `Get` query only contains queryItems, it should not be `Encodable`
extension RequestQuery where Self: Encodable {
var contentType: String? {
return "application/json; charset=utf-8"
}
var body: Data? {
return try? Mastodon.API.encoder.encode(self)
}
@ -30,18 +40,20 @@ protocol GetQuery: RequestQuery { }
extension GetQuery {
// By default a `GetQuery` does not has data body
var body: Data? { nil }
var contentType: String? { nil }
}
protocol PostQuery: RequestQuery & Encodable { }
protocol PostQuery: RequestQuery { }
extension PostQuery {
// By default a `GetQuery` does not has query items
// By default a `PostQuery` does not has query items
var queryItems: [URLQueryItem]? { nil }
}
protocol PatchQuery: RequestQuery & Encodable { }
protocol PatchQuery: RequestQuery { }
extension PatchQuery {
// By default a `GetQuery` does not has query items
// By default a `PatchQuery` does not has query items
var queryItems: [URLQueryItem]? { nil }
}

View File

@ -8,9 +8,11 @@
import os.log
import XCTest
import Combine
import UIKit
@testable import MastodonSDK
extension MastodonSDKTests {
func testVerifyCredentials() throws {
let theExpectation = expectation(description: "Verify Account Credentials")
@ -44,11 +46,14 @@ extension MastodonSDKTests {
.flatMap({ (result) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Account>, Error> in
// TODO: replace with test account acct
XCTAssertEqual(result.value.acct, "")
XCTAssert(!result.value.acct.isEmpty)
theExpectation1.fulfill()
var query = Mastodon.API.Account.UpdateCredentialQuery()
query.note = dateString
let query = Mastodon.API.Account.UpdateCredentialQuery(
bot: !(result.value.bot ?? false),
note: dateString,
header: Mastodon.Query.MediaAttachment.jpeg(UIImage(systemName: "house")!.jpegData(compressionQuality: 0.8))
)
return Mastodon.API.Account.updateCredentials(session: self.session, domain: self.domain, query: query, authorization: authorization)
})
.sink { completion in
@ -73,8 +78,7 @@ extension MastodonSDKTests {
func testRetrieveAccountInfo() throws {
let theExpectation = expectation(description: "Verify Account Credentials")
let query = Mastodon.API.Account.AccountInfoQuery(id: "1")
Mastodon.API.Account.accountInfo(session: session, domain: domain, query: query, authorization: nil)
Mastodon.API.Account.accountInfo(session: session, domain: "mastodon.online", userID: "1", authorization: nil)
.receive(on: DispatchQueue.main)
.sink { completion in
switch completion {
@ -91,4 +95,5 @@ extension MastodonSDKTests {
wait(for: [theExpectation], timeout: 5.0)
}
}