From dc0e559df384f61249c36f79fc136b89880cab47 Mon Sep 17 00:00:00 2001 From: leogdion Date: Wed, 2 Dec 2020 16:45:37 -0500 Subject: [PATCH] Release/0.2.0 (#79) * refactoring packages * adding table of contents * Fixed Rocket + Github Integration (#76) * removing content type from MKDatabase extension * Updated README file --- .github/workflows/macOS.yml | 4 +- .gitignore | 1 + Documentation/Reference/MistKitNIO/README.md | 28 +++ .../MistKitNIO/classes/HTTPHandler.md | 38 ++++ .../classes/MKNIOHTTP1TokenClient.md | 20 ++ .../Reference/MistKitNIO/enums/BindTo.md | 26 +++ .../extensions/EventLoopFuture.md | 0 .../extensions/MKDatabase.md | 11 +- .../MistKitNIO/structs/MKAsyncClient.md | 27 +++ .../MistKitNIO/structs/MKAsyncRequest.md | 33 +++ .../MistKitNIO/structs/MKAsyncResponse.md | 32 +++ .../typealiases/HTTPHandler.InboundIn.md | 7 + .../typealiases/HTTPHandler.OutboundOut.md | 7 + .../Reference/MistKitVapor/README.md | 5 - Package.swift | 36 ++-- README.md | 203 ++++++++++++++++-- .../BindTo.swift | 0 .../HTTPHandler.swift | 0 .../MKAsyncClient.swift | 0 .../MKAsyncRequest.swift | 14 +- .../MKAsyncResponse.swift | 4 +- .../MKDatabase.swift | 16 +- .../MKNIOHTTP1Error.swift | 0 .../MKNIOHTTP1TokenClient.swift | 0 .../mistdemoc/Commands/DeleteCommand.swift | 2 +- Sources/mistdemoc/Commands/FindCommand.swift | 3 +- Sources/mistdemoc/Commands/ListCommand.swift | 3 +- .../mistdemoc/Commands/MistDemoCommand.swift | 2 +- Sources/mistdemoc/Commands/NewCommand.swift | 3 +- .../mistdemoc/Commands/RenameCommand.swift | 2 +- .../mistdemoc/Commands/WhoAmICommand.swift | 3 +- Sources/mistdemoc/main.swift | 1 - .../Controllers/UsersController.swift | 2 +- .../mistdemod/Extensions/Application.swift | 7 + Sources/mistdemod/Extensions/MKDatabase.swift | 26 +++ Sources/mistdemod/Extensions/Request.swift | 9 + Sources/mistdemod/main.swift | 38 ---- 37 files changed, 499 insertions(+), 114 deletions(-) create mode 100644 Documentation/Reference/MistKitNIO/README.md create mode 100644 Documentation/Reference/MistKitNIO/classes/HTTPHandler.md create mode 100644 Documentation/Reference/MistKitNIO/classes/MKNIOHTTP1TokenClient.md create mode 100644 Documentation/Reference/MistKitNIO/enums/BindTo.md rename Documentation/Reference/{MistKitVapor => MistKitNIO}/extensions/EventLoopFuture.md (100%) rename Documentation/Reference/{MistKitVapor => MistKitNIO}/extensions/MKDatabase.md (68%) create mode 100644 Documentation/Reference/MistKitNIO/structs/MKAsyncClient.md create mode 100644 Documentation/Reference/MistKitNIO/structs/MKAsyncRequest.md create mode 100644 Documentation/Reference/MistKitNIO/structs/MKAsyncResponse.md create mode 100644 Documentation/Reference/MistKitNIO/typealiases/HTTPHandler.InboundIn.md create mode 100644 Documentation/Reference/MistKitNIO/typealiases/HTTPHandler.OutboundOut.md rename Sources/{MistKitNIOHTTP1Token => MistKitNIO}/BindTo.swift (100%) rename Sources/{MistKitNIOHTTP1Token => MistKitNIO}/HTTPHandler.swift (100%) rename Sources/{MistKitNIOClient => MistKitNIO}/MKAsyncClient.swift (100%) rename Sources/{MistKitNIOClient => MistKitNIO}/MKAsyncRequest.swift (69%) rename Sources/{MistKitNIOClient => MistKitNIO}/MKAsyncResponse.swift (89%) rename Sources/{MistKitVapor => MistKitNIO}/MKDatabase.swift (79%) rename Sources/{MistKitNIOHTTP1Token => MistKitNIO}/MKNIOHTTP1Error.swift (100%) rename Sources/{MistKitNIOHTTP1Token => MistKitNIO}/MKNIOHTTP1TokenClient.swift (100%) create mode 100644 Sources/mistdemod/Extensions/Application.swift create mode 100644 Sources/mistdemod/Extensions/MKDatabase.swift create mode 100644 Sources/mistdemod/Extensions/Request.swift diff --git a/.github/workflows/macOS.yml b/.github/workflows/macOS.yml index 3098087..bdea64b 100644 --- a/.github/workflows/macOS.yml +++ b/.github/workflows/macOS.yml @@ -6,6 +6,7 @@ on: - '*' - 'feature/*' - 'release/*' + tags: '*' jobs: build: @@ -32,6 +33,7 @@ jobs: - name: Build run: swift build - name: Lint + if: startsWith(github.ref, 'refs/tags/') != true run: swift run swiftformat --lint . && swift run swiftlint - name: Run tests run: swift test -v --enable-code-coverage @@ -42,7 +44,7 @@ jobs: env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} - name: Build Documentation - if: ${{ matrix.xcode == '/Applications/Xcode_12.3.app' }} + if: ${{ matrix.xcode == '/Applications/Xcode_12.3.app' && !startsWith(github.ref, 'refs/tags/') }} run: | swift run sourcedocs generate build -cra git config --local user.email "action@github.com" diff --git a/.gitignore b/.gitignore index 669f34d..1ffb7cf 100644 --- a/.gitignore +++ b/.gitignore @@ -166,3 +166,4 @@ Pods *.lcov Brewfile.lock.json Package.resolved +gh-md-toc diff --git a/Documentation/Reference/MistKitNIO/README.md b/Documentation/Reference/MistKitNIO/README.md new file mode 100644 index 0000000..344c517 --- /dev/null +++ b/Documentation/Reference/MistKitNIO/README.md @@ -0,0 +1,28 @@ +# Reference Documentation + +## Structs + +- [MKAsyncClient](structs/MKAsyncClient.md) +- [MKAsyncRequest](structs/MKAsyncRequest.md) +- [MKAsyncResponse](structs/MKAsyncResponse.md) + +## Classes + +- [HTTPHandler](classes/HTTPHandler.md) +- [MKNIOHTTP1TokenClient](classes/MKNIOHTTP1TokenClient.md) + +## Enums + +- [BindTo](enums/BindTo.md) + +## Extensions + +- [EventLoopFuture](extensions/EventLoopFuture.md) +- [MKDatabase](extensions/MKDatabase.md) + +## Typealiases + +- [HTTPHandler.InboundIn](typealiases/HTTPHandler.InboundIn.md) +- [HTTPHandler.OutboundOut](typealiases/HTTPHandler.OutboundOut.md) + +This file was generated by [SourceDocs](https://github.com/eneko/SourceDocs) \ No newline at end of file diff --git a/Documentation/Reference/MistKitNIO/classes/HTTPHandler.md b/Documentation/Reference/MistKitNIO/classes/HTTPHandler.md new file mode 100644 index 0000000..ce3692f --- /dev/null +++ b/Documentation/Reference/MistKitNIO/classes/HTTPHandler.md @@ -0,0 +1,38 @@ +**CLASS** + +# `HTTPHandler` + +```swift +public final class HTTPHandler: ChannelInboundHandler +``` + +## Methods +### `init(fileIO:htdocsPath:channel:_:)` + +```swift +public init(fileIO: NonBlockingFileIO, htdocsPath: String, channel: Channel, _ onToken: @escaping (String) -> Void) +``` + +### `channelRead(context:data:)` + +```swift +public func channelRead(context: ChannelHandlerContext, data: NIOAny) +``` + +#### Parameters + +| Name | Description | +| ---- | ----------- | +| context | The `ChannelHandlerContext` which this `ChannelHandler` belongs to. | +| data | The data read from the remote peer, wrapped in a `NIOAny`. | + +### `startServer(htdocs:allowHalfClosure:bindTarget:_:)` + +```swift +public static func startServer( + htdocs: String, + allowHalfClosure: Bool, + bindTarget: BindTo, + _ callback: @escaping (EventLoop, String) -> Void +) throws -> Channel +``` diff --git a/Documentation/Reference/MistKitNIO/classes/MKNIOHTTP1TokenClient.md b/Documentation/Reference/MistKitNIO/classes/MKNIOHTTP1TokenClient.md new file mode 100644 index 0000000..4ee329b --- /dev/null +++ b/Documentation/Reference/MistKitNIO/classes/MKNIOHTTP1TokenClient.md @@ -0,0 +1,20 @@ +**CLASS** + +# `MKNIOHTTP1TokenClient` + +```swift +public class MKNIOHTTP1TokenClient: MKTokenClient +``` + +## Methods +### `init(bindTo:onRedirectURL:)` + +```swift +public init(bindTo: BindTo, onRedirectURL: ((URL) -> Void)? = nil) +``` + +### `request(_:_:)` + +```swift +public func request(_ request: MKAuthenticationResponse?, _ callback: @escaping ((Result) -> Void)) +``` diff --git a/Documentation/Reference/MistKitNIO/enums/BindTo.md b/Documentation/Reference/MistKitNIO/enums/BindTo.md new file mode 100644 index 0000000..e32cc8f --- /dev/null +++ b/Documentation/Reference/MistKitNIO/enums/BindTo.md @@ -0,0 +1,26 @@ +**ENUM** + +# `BindTo` + +```swift +public enum BindTo +``` + +## Cases +### `ipAddress(host:port:)` + +```swift +case ipAddress(host: String, port: Int) +``` + +### `unixDomainSocket(path:)` + +```swift +case unixDomainSocket(path: String) +``` + +### `stdio` + +```swift +case stdio +``` diff --git a/Documentation/Reference/MistKitVapor/extensions/EventLoopFuture.md b/Documentation/Reference/MistKitNIO/extensions/EventLoopFuture.md similarity index 100% rename from Documentation/Reference/MistKitVapor/extensions/EventLoopFuture.md rename to Documentation/Reference/MistKitNIO/extensions/EventLoopFuture.md diff --git a/Documentation/Reference/MistKitVapor/extensions/MKDatabase.md b/Documentation/Reference/MistKitNIO/extensions/MKDatabase.md similarity index 68% rename from Documentation/Reference/MistKitVapor/extensions/MKDatabase.md rename to Documentation/Reference/MistKitNIO/extensions/MKDatabase.md index d23716f..a5fb502 100644 --- a/Documentation/Reference/MistKitVapor/extensions/MKDatabase.md +++ b/Documentation/Reference/MistKitNIO/extensions/MKDatabase.md @@ -9,30 +9,29 @@ public extension MKDatabase ### `query(_:on:)` ```swift -func query( +func query( _ query: FetchRecordQueryRequest>, on eventLoop: EventLoop -) -> EventLoopFuture<[RecordType]> where RecordType.ContentType == EncodedType +) -> EventLoopFuture<[RecordType]> ``` ### `perform(operations:on:)` ```swift -func perform( +func perform( operations: ModifyRecordQueryRequest, on eventLoop: EventLoop ) -> EventLoopFuture> - where RecordType.ContentType == EncodedType ``` ### `lookup(_:on:)` ```swift -func lookup( +func lookup( _ lookup: LookupRecordQueryRequest, on eventLoop: EventLoop -) -> EventLoopFuture<[RecordType]> where RecordType.ContentType == EncodedType +) -> EventLoopFuture<[RecordType]> ``` ### `perform(request:returnFailedAuthentication:on:)` diff --git a/Documentation/Reference/MistKitNIO/structs/MKAsyncClient.md b/Documentation/Reference/MistKitNIO/structs/MKAsyncClient.md new file mode 100644 index 0000000..7b5dc11 --- /dev/null +++ b/Documentation/Reference/MistKitNIO/structs/MKAsyncClient.md @@ -0,0 +1,27 @@ +**STRUCT** + +# `MKAsyncClient` + +```swift +public struct MKAsyncClient: MKHttpClient +``` + +## Properties +### `client` + +```swift +public let client: HTTPClient +``` + +## Methods +### `init(client:)` + +```swift +public init(client: HTTPClient) +``` + +### `request(withURL:data:)` + +```swift +public func request(withURL url: URL, data: Data?) -> MKAsyncRequest +``` diff --git a/Documentation/Reference/MistKitNIO/structs/MKAsyncRequest.md b/Documentation/Reference/MistKitNIO/structs/MKAsyncRequest.md new file mode 100644 index 0000000..c02c979 --- /dev/null +++ b/Documentation/Reference/MistKitNIO/structs/MKAsyncRequest.md @@ -0,0 +1,33 @@ +**STRUCT** + +# `MKAsyncRequest` + +```swift +public struct MKAsyncRequest: MKHttpRequest +``` + +## Properties +### `client` + +```swift +public let client: HTTPClient +``` + +### `url` + +```swift +public let url: URL +``` + +### `data` + +```swift +public let data: Data? +``` + +## Methods +### `execute(_:)` + +```swift +public func execute(_ callback: @escaping ((Result) -> Void)) +``` diff --git a/Documentation/Reference/MistKitNIO/structs/MKAsyncResponse.md b/Documentation/Reference/MistKitNIO/structs/MKAsyncResponse.md new file mode 100644 index 0000000..36e4f50 --- /dev/null +++ b/Documentation/Reference/MistKitNIO/structs/MKAsyncResponse.md @@ -0,0 +1,32 @@ +**STRUCT** + +# `MKAsyncResponse` + +```swift +public struct MKAsyncResponse: MKHttpResponse +``` + +## Properties +### `response` + +```swift +public let response: HTTPClient.Response +``` + +### `body` + +```swift +public var body: Data? +``` + +### `status` + +```swift +public var status: Int +``` + +### `webAuthenticationToken` + +```swift +public var webAuthenticationToken: String? +``` diff --git a/Documentation/Reference/MistKitNIO/typealiases/HTTPHandler.InboundIn.md b/Documentation/Reference/MistKitNIO/typealiases/HTTPHandler.InboundIn.md new file mode 100644 index 0000000..92e6113 --- /dev/null +++ b/Documentation/Reference/MistKitNIO/typealiases/HTTPHandler.InboundIn.md @@ -0,0 +1,7 @@ +**TYPEALIAS** + +# `HTTPHandler.InboundIn` + +```swift +public typealias InboundIn = HTTPServerRequestPart +``` diff --git a/Documentation/Reference/MistKitNIO/typealiases/HTTPHandler.OutboundOut.md b/Documentation/Reference/MistKitNIO/typealiases/HTTPHandler.OutboundOut.md new file mode 100644 index 0000000..b06ff7a --- /dev/null +++ b/Documentation/Reference/MistKitNIO/typealiases/HTTPHandler.OutboundOut.md @@ -0,0 +1,7 @@ +**TYPEALIAS** + +# `HTTPHandler.OutboundOut` + +```swift +public typealias OutboundOut = HTTPServerResponsePart +``` diff --git a/Documentation/Reference/MistKitVapor/README.md b/Documentation/Reference/MistKitVapor/README.md index 3d9a819..cea5ec1 100644 --- a/Documentation/Reference/MistKitVapor/README.md +++ b/Documentation/Reference/MistKitVapor/README.md @@ -15,9 +15,4 @@ - [MKVaporModelStorage](classes/MKVaporModelStorage.md) - [MKVaporSessionStorage](classes/MKVaporSessionStorage.md) -## Extensions - -- [EventLoopFuture](extensions/EventLoopFuture.md) -- [MKDatabase](extensions/MKDatabase.md) - This file was generated by [SourceDocs](https://github.com/eneko/SourceDocs) \ No newline at end of file diff --git a/Package.swift b/Package.swift index e5dc5f3..d9f3177 100644 --- a/Package.swift +++ b/Package.swift @@ -13,20 +13,16 @@ let package = Package( targets: ["MistKit"] ), .library( - name: "MistKitNIOHTTP1Token", - targets: ["MistKitNIOHTTP1Token"] - ), - .library( - name: "MistKitSwifter", - targets: ["MistKitSwifter"] + name: "MistKitNIO", + targets: ["MistKitNIO"] ), .library( name: "MistKitVapor", targets: ["MistKitVapor"] ), .library( - name: "MistKitNIOClient", - targets: ["MistKitNIOClient"] + name: "MistKitSwifter", + targets: ["MistKitSwifter"] ), .executable(name: "mistdemoc", targets: ["mistdemoc"]), .executable(name: "mistdemod", targets: ["mistdemod"]) @@ -42,11 +38,11 @@ let package = Package( .package(url: "https://github.com/vapor/fluent-sqlite-driver.git", from: "4.0.0"), .package(url: "https://github.com/swift-server/async-http-client.git", from: "1.0.0"), // dev - .package(url: "https://github.com/shibapm/Komondor", from: "1.0.6"), - .package(url: "https://github.com/eneko/SourceDocs", from: "1.2.1"), - .package(url: "https://github.com/nicklockwood/SwiftFormat", from: "0.47.0"), - .package(url: "https://github.com/realm/SwiftLint", from: "0.41.0"), - .package(url: "https://github.com/brightdigit/Rocket", .branch("feature/yams-4.0.0")) + .package(url: "https://github.com/shibapm/Komondor", from: "1.0.6"), // dev + .package(url: "https://github.com/eneko/SourceDocs", from: "1.2.1"), // dev + .package(url: "https://github.com/nicklockwood/SwiftFormat", from: "0.47.0"), // dev + .package(url: "https://github.com/realm/SwiftLint", from: "0.41.0"), // dev + .package(url: "https://github.com/brightdigit/Rocket", .branch("feature/yams-4.0.0")) // dev ], targets: [ // Targets are the basic building blocks of a package. A target can define a module or a test suite. @@ -55,11 +51,12 @@ let package = Package( name: "MistKit", dependencies: [] ), - .target(name: "MistKitNIOHTTP1Token", + .target(name: "MistKitNIO", dependencies: [ "MistKit", .product(name: "NIO", package: "swift-nio"), - .product(name: "NIOHTTP1", package: "swift-nio") + .product(name: "NIOHTTP1", package: "swift-nio"), + .product(name: "AsyncHTTPClient", package: "async-http-client") ]), .target(name: "MistKitSwifter", dependencies: [ @@ -69,16 +66,11 @@ let package = Package( .target(name: "MistKitVapor", dependencies: [ "MistKit", + "MistKitNIO", .product(name: "Vapor", package: "vapor"), .product(name: "Fluent", package: "fluent") ]), - - .target(name: "MistKitNIOClient", - dependencies: [ - "MistKit", - .product(name: "AsyncHTTPClient", package: "async-http-client") - ]), - .target(name: "mistdemoc", dependencies: ["MistKit", "MistKitNIOHTTP1Token", "MistKitDemo", + .target(name: "mistdemoc", dependencies: ["MistKit", "MistKitNIO", "MistKitDemo", .product(name: "ArgumentParser", package: "swift-argument-parser")]), .target(name: "mistdemod", dependencies: ["MistKit", "MistKitVapor", "MistKitDemo", .product(name: "Vapor", package: "vapor"), diff --git a/README.md b/README.md index 35f33b9..5471c4d 100644 --- a/README.md +++ b/README.md @@ -43,12 +43,15 @@ Swift Package for Server-Side and Command-Line Access to CloudKit Web Services * [Fetching Records by Record Name (records/lookup)](#fetching-records-by-record-name-recordslookup) * [Fetching Current User Identity (users/caller)](#fetching-current-user-identity-userscaller) * [Modifying Records (records/modify)](#modifying-records-recordsmodify) + * [Using SwiftNIO](#using-swiftnio) + * [Using EventLoops](#using-eventloops) + * [Choosing an HTTP Client](#choosing-an-http-client) * [Examples](#examples) * [Further Code Documentation](#further-code-documentation) * [**Roadmap**](#roadmap) * [~~0.1.0~~](#010) - * [**0.2.0**](#020) - * [0.4.0](#040) + * [~~0.2.0~~](#020) + * [**0.4.0**](#040) * [0.6.0](#060) * [0.8.0](#080) * [0.9.0](#090) @@ -148,7 +151,7 @@ To integrate **MistKit** into your project using SPM, specify it in your Package let package = Package( ... dependencies: [ - .package(url: "https://github.com/brightdigit/MistKit", .branch("main") + .package(url: "https://github.com/brightdigit/MistKit", from: "0.2.0") ], targets: [ .target( @@ -159,6 +162,18 @@ let package = Package( ) ``` +There are also products for SwiftNIO as well as Vapor if you are building server-side implmentation: + +```swift + .target( + name: "YourTarget", + dependencies: ["MistKit", + .product(name: "MistKitNIO", package: "MistKit"), // if you are building a server-side application + .product(name: "MistKitVapor", package: "MistKit") // if you are building a Vapor application + ...] + ), +``` + # Usage ## Composing Web Service Requests @@ -200,14 +215,19 @@ Once that's setup, you can setup a `MKTokenManager`. #### Managing Web Authentication Tokens -`MKTokenManager` requires two components: - -* `MKTokenStorage` stores the token for later +`MKTokenManager` requires a `MKTokenStorage` for storing the token for later. +There are a few implementations you can use: * `MKFileStorage` stores the token as a simple text file * `MKUserDefaultsStorage` stores the token using `UserDefaults` -* `MKTokenClient` sets up an http server for listening for the web authentication token + * `MKVaporModelStorage` stores the token in a database `Model` object via `Fluent` + * `MKVaporSessionStorage` stores the token the Vapor `Session` data + +Optionally **MistKit** can setup a web server for you if needed to listen to web authentication via a `MKTokenClient`: +There are a few implementations you can use: * `MKNIOHTTP1TokenClient` sets up an http server using SwiftNIO +Here's an example of how you `MKDatabase`: + ```swift let connection = MKDatabaseConnection( container: options.container, @@ -230,7 +250,7 @@ let database = MKDatabase( ) ``` -If you already have access to the `webAuthenticationToken`, you can set the token in the `MKTokenManager` at any point in your application: +If you already have access to the `webAuthenticationToken`, you can always set the token in the `MKTokenManager` at any point in your application: ```swift // setup how to manager your user's web authentication token @@ -247,11 +267,80 @@ if let token = options.token { ... ``` -A great example of this is if you are already, logged in an iPhone app using CloudKit and wish to [save the webAuthenticationToken from `CKFetchWebAuthTokenOperation` into a database using server-side swift](https://developer.apple.com/documentation/cloudkit/ckfetchwebauthtokenoperation). +##### Managing Tokens in Vapor + +In the `mistdemod` demo Vapor application, there's an example of how to create an `MKDatabase` based on the request using both `MKVaporModelStorage` and `MKVaporSessionStorage`: + +```swift +extension MKDatabase where HttpClient == MKVaporClient { + init(request: Request) { + let storage: MKTokenStorage + if let user = request.auth.get(User.self) { + storage = MKVaporModelStorage(model: user) + } else { + storage = MKVaporSessionStorage(session: request.session) + } + let manager = MKTokenManager(storage: storage, client: nil) + + let options = MistDemoDefaultConfiguration(apiKey: request.application.cloudKitAPIKey) + let connection = MKDatabaseConnection(container: options.container, apiToken: options.apiKey, environment: options.environment) + + // use the webAuthenticationToken which is passed + if let token = options.token { + manager.webAuthenticationToken = token + } + + self.init(connection: connection, factory: nil, client: MKVaporClient(client: request.client), tokenManager: manager) + } +} +``` + +In this case, for the `User` model needs to implement `MKModelStorable`. + +```swift +final class User: Model, Content { + ... + + @Field(key: "cloudKitToken") + var cloudKitToken: String? +} + +extension User: MKModelStorable { + static var tokenKey: KeyPath> = \User.$cloudKitToken +} +``` + +The `MKModelStorable` protocol ensures that the `Model` contains the properties needed for storing the web authentication token. + +While the command line tool needs a `MKTokenClient` to listen for the callback from CloudKit, with a server-side application you can just add a API call. Here's an example which listens for the `ckWebAuthToken` and saves it to the `User`: + +```swift +struct CloudKitController: RouteCollection { + func token(_ request: Request) -> EventLoopFuture { + guard let token: String = request.query["ckWebAuthToken"] else { + return request.eventLoop.makeSucceededFuture(.notFound) + } + + guard let user = request.auth.get(User.self) else { + request.cloudKitAPI.webAuthenticationToken = token + return request.eventLoop.makeSucceededFuture(.accepted) + } + + user.cloudKitToken = token + return user.save(on: request.db).transform(to: .accepted) + } + + func boot(routes: RoutesBuilder) throws { + routes.get(["token"], use: token) + } +} +``` + +If you have an app which already uses Apple's existing CloudKit API, you can also [save the webAuthenticationToken to your database with a `CKFetchWebAuthTokenOperation`](https://developer.apple.com/documentation/cloudkit/ckfetchwebauthtokenoperation). ##### Using `MKNIOHTTP1TokenClient` -To use `MKNIOHTTP1TokenClient`, add `MistKitNIOHTTP1Token` to your package dependency: +If you are not building a server-side application, you can use `MKNIOHTTP1TokenClient`, by adding `MistKitNIO` to your package dependency: ```swift let package = Package( @@ -517,9 +606,93 @@ database.lookup(request) { result in } ``` +## Using SwiftNIO + +If you are building a server-side application and already using [SwiftNIO](https://github.com/apple/swift-nio), you might want to take advantage of some helpers which will work already existing patterns and APIs available. Primarily **[EventLoops](https://apple.github.io/swift-nio/docs/current/NIO/Protocols/EventLoop.html)** from [SwiftNIO](https://github.com/apple/swift-nio) and the respective **HTTP clients** from [SwiftNIO](https://github.com/apple/swift-nio) and [Vapor](https://vapor.codes/). + +### Using EventLoops + +If you are building a server-side application in [SwiftNIO](https://github.com/apple/swift-nio) (or [Vapor](https://vapor.codes/)), you are likely using [EventLoops](https://apple.github.io/swift-nio/docs/current/NIO/Protocols/EventLoop.html) and [EventLoopFuture](https://apple.github.io/swift-nio/docs/current/NIO/Classes/EventLoopFuture.html) for asyncronous programming. EventLoopFutures are essentially the Future/Promise implementation of [SwiftNIO](https://github.com/apple/swift-nio). Luckily there are helper methods in MistKit which provide [EventLoopFutures](https://apple.github.io/swift-nio/docs/current/NIO/Classes/EventLoopFuture.html) similar to the way they implmented in [SwiftNIO](https://github.com/apple/swift-nio). These implementations augment the already existing callback: + + +```swift +public extension MKDatabase { + func query( + _ query: FetchRecordQueryRequest>, + on eventLoop: EventLoop + ) -> EventLoopFuture<[RecordType]> + + func perform( + operations: ModifyRecordQueryRequest, + on eventLoop: EventLoop + ) -> EventLoopFuture> + + func lookup( + _ lookup: LookupRecordQueryRequest, + on eventLoop: EventLoop + ) -> EventLoopFuture<[RecordType]> + + func perform( + request: RequestType, + on eventLoop: EventLoop + ) -> EventLoopFuture -> EventLoopFuture + where RequestType.Response == ResponseType +} +``` + +Also if you are using the results as `Content` for a [Vapor](https://vapor.codes/) HTTP response, **MistKit** provides a `MKServerResponse` enum type which distinguishes between an authentication failure (with the redirect URL) and an actual success. + +```swift +public enum MKServerResponse: Codable where Success: Codable { + public init(attemptRecoveryFrom error: Error) throws + + case failure(URL) + case success(Success) +} +``` + +Besides [EventLoopFuture](https://apple.github.io/swift-nio/docs/current/NIO/Classes/EventLoopFuture.html), you can also use a different HTTP client for calling CloudKit Web Services. + +### Choosing an HTTP Client + +By default, MistKit uses `URLSession` for making HTTP calls to the CloudKit Web Service via the `MKURLSessionClient`: + +```swift +public struct MKURLSessionClient: MKHttpClient { + public init(session: URLSession) { + self.session = session + } + + public func request(withURL url: URL, data: Data?) -> MKURLRequest +} +``` + +However if you are using [SwiftNIO](https://github.com/apple/swift-nio) or [Vapor](https://vapor.codes/), it makes more sense the use their HTTP clients for making those calls: +* For **SwiftNIO**, there's **`MKAsyncClient`** which uses an `HTTPClient` provided by the `AsyncHTTPClient` library +* For **Vapor**, there's **`MKVaporClient`** which uses an `Client` provided by the `Vapor` library + +In the mistdemod example, you can see how to use a Vapor `Request` to create an `MKDatabase` with the `client` property of the `Request`: + +```swift +extension MKDatabase where HttpClient == MKVaporClient { + init(request: Request) { + let manager: MKTokenManager + let connection : MKDatabaseConnection + self.init( + connection: connection, + factory: nil, + client: MKVaporClient(client: request.client), + tokenManager: manager + ) + } +} +``` + ## Examples -For now checkout [the `mistdemoc` Swift package executable here](https://github.com/brightdigit/MistKit/tree/main/Sources/mistdemoc) on how to do basic CRUD methods in CloudKit via MistKit. +There are two examples on how to do basic CRUD methods in CloudKit via MistKit: +* As a command line tool using Swift Argument Parser checkout [the `mistdemoc` Swift package executable here](https://github.com/brightdigit/MistKit/tree/main/Sources/mistdemoc) +* And a server-side Vapor application [`mistdemod` here](https://github.com/brightdigit/MistKit/tree/main/Sources/mistdemoc) ## Further Code Documentation @@ -539,10 +712,10 @@ For now checkout [the `mistdemoc` Swift package executable here](https://github. ## 0.2.0 -- [ ] Vapor Token Client -- [ ] Vapor Token Storage -- [ ] Vapor URL Client -- [ ] Swift NIO URL Client +- [x] Vapor Token Client +- [x] Vapor Token Storage +- [x] Vapor URL Client +- [x] Swift NIO URL Client ## 0.4.0 diff --git a/Sources/MistKitNIOHTTP1Token/BindTo.swift b/Sources/MistKitNIO/BindTo.swift similarity index 100% rename from Sources/MistKitNIOHTTP1Token/BindTo.swift rename to Sources/MistKitNIO/BindTo.swift diff --git a/Sources/MistKitNIOHTTP1Token/HTTPHandler.swift b/Sources/MistKitNIO/HTTPHandler.swift similarity index 100% rename from Sources/MistKitNIOHTTP1Token/HTTPHandler.swift rename to Sources/MistKitNIO/HTTPHandler.swift diff --git a/Sources/MistKitNIOClient/MKAsyncClient.swift b/Sources/MistKitNIO/MKAsyncClient.swift similarity index 100% rename from Sources/MistKitNIOClient/MKAsyncClient.swift rename to Sources/MistKitNIO/MKAsyncClient.swift diff --git a/Sources/MistKitNIOClient/MKAsyncRequest.swift b/Sources/MistKitNIO/MKAsyncRequest.swift similarity index 69% rename from Sources/MistKitNIOClient/MKAsyncRequest.swift rename to Sources/MistKitNIO/MKAsyncRequest.swift index ad5b976..20b94c4 100644 --- a/Sources/MistKitNIOClient/MKAsyncRequest.swift +++ b/Sources/MistKitNIO/MKAsyncRequest.swift @@ -3,27 +3,25 @@ import Foundation import MistKit public struct MKAsyncRequest: MKHttpRequest { - let client: HTTPClient - let url: URL - let data: Data? + public let client: HTTPClient + public let url: URL + public let data: Data? public func execute(_ callback: @escaping ((Result) -> Void)) { - // var clientRequest = HTTPClient.Request(url: url) - - // return MKVaporClientRequest(client: client, request: clientRequest) - var request: HTTPClient.Request + do { request = try HTTPClient.Request(url: url, method: data == nil ? .GET : .POST) } catch { callback(.failure(error)) return } + if let data = data { request.body = .data(data) request.headers.add(name: "Content-Type", value: "application/json") - // request.headers.add(name: .contentType, value: "application/json") } + client.execute(request: request).map(MKAsyncResponse.init).whenComplete(callback) } } diff --git a/Sources/MistKitNIOClient/MKAsyncResponse.swift b/Sources/MistKitNIO/MKAsyncResponse.swift similarity index 89% rename from Sources/MistKitNIOClient/MKAsyncResponse.swift rename to Sources/MistKitNIO/MKAsyncResponse.swift index 3de5f1c..0887321 100644 --- a/Sources/MistKitNIOClient/MKAsyncResponse.swift +++ b/Sources/MistKitNIO/MKAsyncResponse.swift @@ -3,6 +3,8 @@ import Foundation import MistKit public struct MKAsyncResponse: MKHttpResponse { + public let response: HTTPClient.Response + public var body: Data? { return response.body.map { Data(buffer: $0) } } @@ -14,6 +16,4 @@ public struct MKAsyncResponse: MKHttpResponse { public var webAuthenticationToken: String? { return response.headers["X-Apple-CloudKit-Web-Auth-Token"].first } - - let response: HTTPClient.Response } diff --git a/Sources/MistKitVapor/MKDatabase.swift b/Sources/MistKitNIO/MKDatabase.swift similarity index 79% rename from Sources/MistKitVapor/MKDatabase.swift rename to Sources/MistKitNIO/MKDatabase.swift index e7c93e0..6a7c184 100644 --- a/Sources/MistKitVapor/MKDatabase.swift +++ b/Sources/MistKitNIO/MKDatabase.swift @@ -2,30 +2,29 @@ import MistKit import NIO public extension MKDatabase { - func query( + func query( _ query: FetchRecordQueryRequest>, on eventLoop: EventLoop - ) -> EventLoopFuture<[RecordType]> where RecordType.ContentType == EncodedType { + ) -> EventLoopFuture<[RecordType]> { let promise = eventLoop.makePromise(of: [RecordType].self) self.query(query, promise.completeWith) return promise.futureResult } - func perform( + func perform( operations: ModifyRecordQueryRequest, on eventLoop: EventLoop ) - -> EventLoopFuture> - where RecordType.ContentType == EncodedType { + -> EventLoopFuture> { let promise = eventLoop.makePromise(of: ModifiedRecordQueryResult.self) perform(operations: operations, promise.completeWith) return promise.futureResult } - func lookup( + func lookup( _ lookup: LookupRecordQueryRequest, on eventLoop: EventLoop - ) -> EventLoopFuture<[RecordType]> where RecordType.ContentType == EncodedType { + ) -> EventLoopFuture<[RecordType]> { let promise = eventLoop.makePromise(of: [RecordType].self) self.lookup(lookup, promise.completeWith) return promise.futureResult @@ -46,7 +45,8 @@ public extension EventLoopFuture { func content() -> EventLoopFuture> where Value == [RecordType], RecordType.ContentType == ContentType { - return mapEach(RecordType.content(fromRecord:)).mistKitResponse() + // return mapEach(RecordType.content(fromRecord:)).mistKitResponse() + return map { $0.map(RecordType.content(fromRecord:)) }.mistKitResponse() } func content() diff --git a/Sources/MistKitNIOHTTP1Token/MKNIOHTTP1Error.swift b/Sources/MistKitNIO/MKNIOHTTP1Error.swift similarity index 100% rename from Sources/MistKitNIOHTTP1Token/MKNIOHTTP1Error.swift rename to Sources/MistKitNIO/MKNIOHTTP1Error.swift diff --git a/Sources/MistKitNIOHTTP1Token/MKNIOHTTP1TokenClient.swift b/Sources/MistKitNIO/MKNIOHTTP1TokenClient.swift similarity index 100% rename from Sources/MistKitNIOHTTP1Token/MKNIOHTTP1TokenClient.swift rename to Sources/MistKitNIO/MKNIOHTTP1TokenClient.swift diff --git a/Sources/mistdemoc/Commands/DeleteCommand.swift b/Sources/mistdemoc/Commands/DeleteCommand.swift index b9966a8..d5ec2f6 100644 --- a/Sources/mistdemoc/Commands/DeleteCommand.swift +++ b/Sources/mistdemoc/Commands/DeleteCommand.swift @@ -2,7 +2,7 @@ import ArgumentParser import Foundation import MistKit import MistKitDemo -import MistKitNIOHTTP1Token +import MistKitNIO extension MistDemoCommand { struct DeleteCommand: ParsableAsyncCommand { diff --git a/Sources/mistdemoc/Commands/FindCommand.swift b/Sources/mistdemoc/Commands/FindCommand.swift index c465791..5eef497 100644 --- a/Sources/mistdemoc/Commands/FindCommand.swift +++ b/Sources/mistdemoc/Commands/FindCommand.swift @@ -2,7 +2,8 @@ import ArgumentParser import Foundation import MistKit import MistKitDemo -import MistKitNIOHTTP1Token +import MistKitNIO + extension MistDemoCommand { struct FindCommand: ParsableAsyncCommand { static var configuration = CommandConfiguration(commandName: "find") diff --git a/Sources/mistdemoc/Commands/ListCommand.swift b/Sources/mistdemoc/Commands/ListCommand.swift index d19d515..56418a0 100644 --- a/Sources/mistdemoc/Commands/ListCommand.swift +++ b/Sources/mistdemoc/Commands/ListCommand.swift @@ -2,7 +2,8 @@ import ArgumentParser import Foundation import MistKit import MistKitDemo -import MistKitNIOHTTP1Token +import MistKitNIO + extension MistDemoCommand { struct ListCommand: ParsableAsyncCommand { static var configuration = CommandConfiguration(commandName: "list") diff --git a/Sources/mistdemoc/Commands/MistDemoCommand.swift b/Sources/mistdemoc/Commands/MistDemoCommand.swift index 95bda6c..8b04d27 100644 --- a/Sources/mistdemoc/Commands/MistDemoCommand.swift +++ b/Sources/mistdemoc/Commands/MistDemoCommand.swift @@ -2,7 +2,7 @@ import ArgumentParser import Foundation import MistKit import MistKitDemo -import MistKitNIOHTTP1Token +import MistKitNIO struct MistDemoCommand: ParsableCommand { static var configuration = CommandConfiguration( diff --git a/Sources/mistdemoc/Commands/NewCommand.swift b/Sources/mistdemoc/Commands/NewCommand.swift index 20d7545..7aae6a3 100644 --- a/Sources/mistdemoc/Commands/NewCommand.swift +++ b/Sources/mistdemoc/Commands/NewCommand.swift @@ -2,7 +2,8 @@ import ArgumentParser import Foundation import MistKit import MistKitDemo -import MistKitNIOHTTP1Token +import MistKitNIO + extension MistDemoCommand { struct NewCommand: ParsableAsyncCommand { static var configuration = CommandConfiguration(commandName: "new") diff --git a/Sources/mistdemoc/Commands/RenameCommand.swift b/Sources/mistdemoc/Commands/RenameCommand.swift index e891715..d479c15 100644 --- a/Sources/mistdemoc/Commands/RenameCommand.swift +++ b/Sources/mistdemoc/Commands/RenameCommand.swift @@ -2,7 +2,7 @@ import ArgumentParser import Foundation import MistKit import MistKitDemo -import MistKitNIOHTTP1Token +import MistKitNIO extension MistDemoCommand { struct RenameCommand: ParsableAsyncCommand { diff --git a/Sources/mistdemoc/Commands/WhoAmICommand.swift b/Sources/mistdemoc/Commands/WhoAmICommand.swift index 8920867..65a3cde 100644 --- a/Sources/mistdemoc/Commands/WhoAmICommand.swift +++ b/Sources/mistdemoc/Commands/WhoAmICommand.swift @@ -2,7 +2,8 @@ import ArgumentParser import Foundation import MistKit import MistKitDemo -import MistKitNIOHTTP1Token +import MistKitNIO + extension MistDemoCommand { struct WhoAmICommand: ParsableAsyncCommand { static var configuration = CommandConfiguration(commandName: "whoami") diff --git a/Sources/mistdemoc/main.swift b/Sources/mistdemoc/main.swift index 4fb0286..7ee2af7 100644 --- a/Sources/mistdemoc/main.swift +++ b/Sources/mistdemoc/main.swift @@ -1,6 +1,5 @@ import ArgumentParser import Foundation import MistKit -import MistKitNIOHTTP1Token MistDemoCommand.main() diff --git a/Sources/mistdemod/Controllers/UsersController.swift b/Sources/mistdemod/Controllers/UsersController.swift index b40a63e..a8ddf40 100644 --- a/Sources/mistdemod/Controllers/UsersController.swift +++ b/Sources/mistdemod/Controllers/UsersController.swift @@ -1,5 +1,5 @@ import MistKit -import MistKitVapor +import MistKitNIO import Vapor struct UsersController: RouteCollection { diff --git a/Sources/mistdemod/Extensions/Application.swift b/Sources/mistdemod/Extensions/Application.swift new file mode 100644 index 0000000..ae9038d --- /dev/null +++ b/Sources/mistdemod/Extensions/Application.swift @@ -0,0 +1,7 @@ +import Vapor + +extension Application { + var cloudKitAPIKey: String { + return "eadc820055c358d8f261d561a93ac2c3ca962d3eca09d8b38c10c1e4beb05844" + } +} diff --git a/Sources/mistdemod/Extensions/MKDatabase.swift b/Sources/mistdemod/Extensions/MKDatabase.swift new file mode 100644 index 0000000..5e104c0 --- /dev/null +++ b/Sources/mistdemod/Extensions/MKDatabase.swift @@ -0,0 +1,26 @@ +import MistKit +import MistKitDemo +import MistKitVapor +import Vapor + +extension MKDatabase where HttpClient == MKVaporClient { + init(request: Request) { + let storage: MKTokenStorage + if let user = request.auth.get(User.self) { + storage = MKVaporModelStorage(model: user) + } else { + storage = request.cloudKitAPI + } + let manager = MKTokenManager(storage: storage, client: nil) + + let options = MistDemoDefaultConfiguration(apiKey: request.application.cloudKitAPIKey) + let connection = MKDatabaseConnection(container: options.container, apiToken: options.apiKey, environment: options.environment) + + // use the webAuthenticationToken which is passed + if let token = options.token { + manager.webAuthenticationToken = token + } + + self.init(connection: connection, factory: nil, client: MKVaporClient(client: request.client), tokenManager: manager) + } +} diff --git a/Sources/mistdemod/Extensions/Request.swift b/Sources/mistdemod/Extensions/Request.swift new file mode 100644 index 0000000..9f23135 --- /dev/null +++ b/Sources/mistdemod/Extensions/Request.swift @@ -0,0 +1,9 @@ +import MistKit +import MistKitVapor +import Vapor + +extension Request { + var cloudKitAPI: MKTokenStorage { + return MKVaporSessionStorage(session: session) + } +} diff --git a/Sources/mistdemod/main.swift b/Sources/mistdemod/main.swift index d36a937..191c58e 100644 --- a/Sources/mistdemod/main.swift +++ b/Sources/mistdemod/main.swift @@ -1,44 +1,6 @@ -import Fluent import FluentSQLiteDriver -import MistKit -import MistKitDemo -import MistKitVapor import Vapor -extension Application { - var cloudKitAPIKey: String { - return "eadc820055c358d8f261d561a93ac2c3ca962d3eca09d8b38c10c1e4beb05844" - } -} - -extension Request { - var cloudKitAPI: MKTokenStorage { - return MKVaporSessionStorage(session: session) - } -} - -extension MKDatabase where HttpClient == MKVaporClient { - init(request: Request) { - let storage: MKTokenStorage - if let user = request.auth.get(User.self) { - storage = MKVaporModelStorage(model: user) - } else { - storage = request.cloudKitAPI - } - let manager = MKTokenManager(storage: storage, client: nil) - - let options = MistDemoDefaultConfiguration(apiKey: request.application.cloudKitAPIKey) - let connection = MKDatabaseConnection(container: options.container, apiToken: options.apiKey, environment: options.environment) - - // use the webAuthenticationToken which is passed - if let token = options.token { - manager.webAuthenticationToken = token - } - - self.init(connection: connection, factory: nil, client: MKVaporClient(client: request.client), tokenManager: manager) - } -} - let app = try Application(.detect()) defer { app.shutdown() } app.databases.use(.sqlite(.memory), as: .sqlite)