Skip to content

Commit 71aac0b

Browse files
committed
Added LockNetService.Client.listEvents()
1 parent feda8cd commit 71aac0b

File tree

4 files changed

+222
-3
lines changed

4 files changed

+222
-3
lines changed

Sources/CoreLock/EventStore.swift

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,7 @@ public extension LockEvent {
6464
public var start: Date?
6565

6666
public var end: Date?
67-
68-
public static var empty: Predicate { return .init(keys: nil, start: nil, end: nil) }
69-
67+
7068
public init(keys: [UUID]?,
7169
start: Date? = nil,
7270
end: Date? = nil) {
@@ -78,6 +76,11 @@ public extension LockEvent {
7876
}
7977
}
8078

79+
public extension LockEvent.Predicate {
80+
81+
static var empty: LockEvent.Predicate { return .init(keys: nil, start: nil, end: nil) }
82+
}
83+
8184
public extension Collection where Self.Element == LockEvent {
8285

8386
func fetch(_ fetchRequest: LockEvent.FetchRequest) -> [LockEvent] {

Sources/CoreLock/EventsRequest.swift

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
//
2+
// EventsRequest.swift
3+
// CoreLock
4+
//
5+
// Created by Alsey Coleman Miller on 10/20/19.
6+
// Copyright © 2019 ColemanCDA. All rights reserved.
7+
//
8+
9+
import Foundation
10+
11+
#if canImport(FoundationNetworking)
12+
import FoundationNetworking
13+
#endif
14+
15+
public struct EventsNetServiceRequest: Equatable {
16+
17+
/// Lock server
18+
public let server: URL
19+
20+
/// Authorization header
21+
public let authorization: LockNetService.Authorization
22+
23+
/// Encrypted request
24+
public let fetchRequest: LockEvent.FetchRequest?
25+
}
26+
27+
// MARK: - URL Request
28+
29+
public extension EventsNetServiceRequest {
30+
31+
func urlRequest() -> URLRequest {
32+
33+
// http://localhost:8080/event
34+
guard var urlComponents = URLComponents(url: server.appendingPathComponent("event"), resolvingAgainstBaseURL: false)
35+
else { fatalError() }
36+
urlComponents.queryItems = fetchRequest?.queryItems
37+
guard let url = urlComponents.url
38+
else { fatalError() }
39+
var urlRequest = URLRequest(url: url)
40+
urlRequest.addValue(authorization.header, forHTTPHeaderField: LockNetService.Authorization.headerField)
41+
urlRequest.httpMethod = "GET"
42+
return urlRequest
43+
}
44+
}
45+
46+
internal extension LockEvent.FetchRequest {
47+
48+
init?(queryItems: [URLQueryItem]) {
49+
guard let offset = queryItems.first(.offset).flatMap(UInt8.init)
50+
else { return nil }
51+
self.offset = offset
52+
self.limit = queryItems.first(.limit).flatMap(UInt8.init)
53+
var predicate = LockEvent.Predicate.empty
54+
predicate.start = queryItems.firstDate(.start)
55+
predicate.end = queryItems.firstDate(.end)
56+
predicate.keys = queryItems
57+
.compactMap(.key)
58+
.compactMap { UUID(uuidString: $0) }
59+
self.predicate = predicate != .empty ? predicate : nil
60+
}
61+
62+
var queryItems: [URLQueryItem] {
63+
var queryItems = [URLQueryItem]()
64+
queryItems.reserveCapacity(3)
65+
queryItems.append(.init(name: .offset, value: offset.description))
66+
limit.flatMap { queryItems.append(.init(name: .limit, value: $0.description)) }
67+
if let predicate = predicate {
68+
predicate.keys.flatMap {
69+
queryItems += $0.map { .init(name: .key, value: $0.uuidString) }
70+
}
71+
predicate.start.flatMap { queryItems.append(.init(name: .start, value: $0)) }
72+
predicate.end.flatMap { queryItems.append(.init(name: .end, value: $0)) }
73+
}
74+
return queryItems
75+
}
76+
}
77+
78+
internal extension EventsNetServiceRequest {
79+
80+
enum QueryItem: String {
81+
case offset
82+
case limit
83+
case key
84+
case start
85+
case end
86+
}
87+
}
88+
89+
internal extension URLQueryItem {
90+
91+
init(name: EventsNetServiceRequest.QueryItem, value: String? = nil) {
92+
self.init(name: name.rawValue, value: value)
93+
}
94+
95+
init(name: EventsNetServiceRequest.QueryItem, value date: Date) {
96+
let value = Int(date.timeIntervalSince1970).description
97+
self.init(name: name, value: value)
98+
}
99+
}
100+
101+
internal extension Sequence where Self.Element == URLQueryItem {
102+
103+
func first(_ name: EventsNetServiceRequest.QueryItem) -> String? {
104+
return first(where: { $0.name == name.rawValue })?.value
105+
}
106+
107+
func compactMap(_ name: EventsNetServiceRequest.QueryItem) -> [String] {
108+
return compactMap { $0.name == name.rawValue ? $0.value : nil }
109+
}
110+
111+
func firstDate(_ name: EventsNetServiceRequest.QueryItem) -> Date? {
112+
guard let value = first(name),
113+
let timeInterval = TimeInterval(value)
114+
else { return nil }
115+
return Date(timeIntervalSince1970: timeInterval)
116+
}
117+
}
118+
119+
// MARK: - Client
120+
121+
public extension LockNetService.Client {
122+
123+
/// Retreive a list of events on device.
124+
func listEvents(fetchRequest: LockEvent.FetchRequest? = nil,
125+
for server: LockNetService,
126+
with key: KeyCredentials,
127+
timeout: TimeInterval = LockNetService.defaultTimeout) throws -> EventsList {
128+
129+
log?("List events for \(server.url.absoluteString)")
130+
131+
let request = EventsNetServiceRequest(
132+
server: server.url,
133+
authorization: LockNetService.Authorization(key: key),
134+
fetchRequest: fetchRequest
135+
)
136+
137+
let (httpResponse, data) = try urlSession.synchronousDataTask(with: request.urlRequest())
138+
139+
guard httpResponse.statusCode == 200
140+
else { throw LockNetService.Error.statusCode(httpResponse.statusCode) }
141+
142+
guard let jsonData = data,
143+
let response = try? jsonDecoder.decode(EventsResponse.self, from: jsonData)
144+
else { throw LockNetService.Error.invalidResponse }
145+
146+
let keys = try response.decrypt(with: key.secret, decoder: jsonDecoder)
147+
return keys
148+
}
149+
}

Sources/CoreLock/EventsResponse.swift

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
//
2+
// EventsResponse.swift
3+
// CoreLock
4+
//
5+
// Created by Alsey Coleman Miller on 10/21/19.
6+
// Copyright © 2019 ColemanCDA. All rights reserved.
7+
//
8+
9+
import Foundation
10+
11+
public struct EventsResponse: Equatable {
12+
13+
public let encryptedData: LockNetService.EncryptedData
14+
}
15+
16+
// MARK: - Codable
17+
18+
extension EventsResponse: Codable {
19+
20+
public init(from decoder: Decoder) throws {
21+
self.encryptedData = try LockNetService.EncryptedData(from: decoder)
22+
}
23+
24+
public func encode(to encoder: Encoder) throws {
25+
try encryptedData.encode(to: encoder)
26+
}
27+
}
28+
29+
// MARK: - Encryption
30+
31+
public extension EventsResponse {
32+
33+
init(encrypt value: EventsList,
34+
with key: KeyData,
35+
encoder: JSONEncoder = JSONEncoder()) throws {
36+
37+
let data = try encoder.encode(value)
38+
self.encryptedData = try .init(encrypt: data, with: key)
39+
}
40+
41+
func decrypt(with key: KeyData,
42+
decoder: JSONDecoder = JSONDecoder()) throws -> EventsList {
43+
44+
let data = try encryptedData.decrypt(with: key)
45+
return try decoder.decode(EventsList.self, from: data)
46+
}
47+
}

Xcode/CoreLock.xcodeproj/project.pbxproj

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,14 @@
7272
6EBA71092357A9870005BEB7 /* LockInformationResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EBA71072357A9870005BEB7 /* LockInformationResponse.swift */; };
7373
6EBA710A2357A9870005BEB7 /* LockInformationResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EBA71072357A9870005BEB7 /* LockInformationResponse.swift */; };
7474
6EBA710B2357A9870005BEB7 /* LockInformationResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EBA71072357A9870005BEB7 /* LockInformationResponse.swift */; };
75+
6EBDB5CB235D5E3200F38CB0 /* EventsRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EBDB5CA235D5E3200F38CB0 /* EventsRequest.swift */; };
76+
6EBDB5CC235D5E3200F38CB0 /* EventsRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EBDB5CA235D5E3200F38CB0 /* EventsRequest.swift */; };
77+
6EBDB5CD235D5E3200F38CB0 /* EventsRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EBDB5CA235D5E3200F38CB0 /* EventsRequest.swift */; };
78+
6EBDB5CE235D5E3200F38CB0 /* EventsRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EBDB5CA235D5E3200F38CB0 /* EventsRequest.swift */; };
79+
6EBDB5D0235D7FF900F38CB0 /* EventsResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EBDB5CF235D7FF900F38CB0 /* EventsResponse.swift */; };
80+
6EBDB5D1235D7FF900F38CB0 /* EventsResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EBDB5CF235D7FF900F38CB0 /* EventsResponse.swift */; };
81+
6EBDB5D2235D7FF900F38CB0 /* EventsResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EBDB5CF235D7FF900F38CB0 /* EventsResponse.swift */; };
82+
6EBDB5D3235D7FF900F38CB0 /* EventsResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EBDB5CF235D7FF900F38CB0 /* EventsResponse.swift */; };
7583
6ED81FE1231662E200B69520 /* CryptoSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 6ED81FE0231662E200B69520 /* CryptoSwift */; };
7684
6ED81FE3231662E200B69520 /* DarwinGATT in Frameworks */ = {isa = PBXBuildFile; productRef = 6ED81FE2231662E200B69520 /* DarwinGATT */; };
7785
6ED81FE5231662E200B69520 /* GATT in Frameworks */ = {isa = PBXBuildFile; productRef = 6ED81FE4231662E200B69520 /* GATT */; };
@@ -292,6 +300,8 @@
292300
6EBA70FD2357A7040005BEB7 /* URLSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSession.swift; sourceTree = "<group>"; };
293301
6EBA71022357A7B40005BEB7 /* LockInformationRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LockInformationRequest.swift; sourceTree = "<group>"; };
294302
6EBA71072357A9870005BEB7 /* LockInformationResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LockInformationResponse.swift; sourceTree = "<group>"; };
303+
6EBDB5CA235D5E3200F38CB0 /* EventsRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventsRequest.swift; sourceTree = "<group>"; };
304+
6EBDB5CF235D7FF900F38CB0 /* EventsResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventsResponse.swift; sourceTree = "<group>"; };
295305
6EE1D6E02357C639004DD856 /* KeysResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeysResponse.swift; sourceTree = "<group>"; };
296306
6EE1D6E92357F1E9004DD856 /* CreateNewKeyRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateNewKeyRequest.swift; sourceTree = "<group>"; };
297307
6EE1D6EE2357FAF2004DD856 /* URLSession.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URLSession.swift; sourceTree = "<group>"; };
@@ -501,6 +511,8 @@
501511
6EF1C63322C9AC9D005E9818 /* ListKeysCharacteristic.swift */,
502512
6E93D353231C624000119F65 /* EventsCharacteristic.swift */,
503513
6E93D352231C623E00119F65 /* ListEventsCharacteristic.swift */,
514+
6EBDB5CA235D5E3200F38CB0 /* EventsRequest.swift */,
515+
6EBDB5CF235D7FF900F38CB0 /* EventsResponse.swift */,
504516
6E93D351231C623B00119F65 /* Notification.swift */,
505517
6EF1C62F22C9AC9D005E9818 /* LockConfiguration.swift */,
506518
6EF1C65222C9AC9F005E9818 /* LockHardware.swift */,
@@ -861,6 +873,7 @@
861873
6EF1C6C122C9AC9F005E9818 /* Version.swift in Sources */,
862874
6EE1D6E12357C639004DD856 /* KeysResponse.swift in Sources */,
863875
6EE1D79F235801BF004DD856 /* EventStore.swift in Sources */,
876+
6EBDB5D0235D7FF900F38CB0 /* EventsResponse.swift in Sources */,
864877
6EACDA99231B353A000CF82A /* Event.swift in Sources */,
865878
6E93D35C231C624300119F65 /* EventsCharacteristic.swift in Sources */,
866879
6EF1C65D22C9AC9F005E9818 /* EncryptedData.swift in Sources */,
@@ -873,6 +886,7 @@
873886
6EF1C66522C9AC9F005E9818 /* AuthenticationError.swift in Sources */,
874887
6EF1C66922C9AC9F005E9818 /* ListKeysCharacteristic.swift in Sources */,
875888
6E93D354231C624300119F65 /* Notification.swift in Sources */,
889+
6EBDB5CB235D5E3200F38CB0 /* EventsRequest.swift in Sources */,
876890
6EF1C6B922C9AC9F005E9818 /* UnlockCharacteristic.swift in Sources */,
877891
6EF1C65922C9AC9F005E9818 /* LockConfiguration.swift in Sources */,
878892
6EF1C6D522C9AC9F005E9818 /* Permission.swift in Sources */,
@@ -924,6 +938,7 @@
924938
6EF1C6C322C9AC9F005E9818 /* Version.swift in Sources */,
925939
6EE1D6E32357C639004DD856 /* KeysResponse.swift in Sources */,
926940
6EE1D7A1235801BF004DD856 /* EventStore.swift in Sources */,
941+
6EBDB5D2235D7FF900F38CB0 /* EventsResponse.swift in Sources */,
927942
6EACDA9B231B353A000CF82A /* Event.swift in Sources */,
928943
6E93D35E231C624300119F65 /* EventsCharacteristic.swift in Sources */,
929944
6EF1C65F22C9AC9F005E9818 /* EncryptedData.swift in Sources */,
@@ -936,6 +951,7 @@
936951
6EF1C66722C9AC9F005E9818 /* AuthenticationError.swift in Sources */,
937952
6EF1C66B22C9AC9F005E9818 /* ListKeysCharacteristic.swift in Sources */,
938953
6E93D356231C624300119F65 /* Notification.swift in Sources */,
954+
6EBDB5CD235D5E3200F38CB0 /* EventsRequest.swift in Sources */,
939955
6EF1C6BB22C9AC9F005E9818 /* UnlockCharacteristic.swift in Sources */,
940956
6EF1C65B22C9AC9F005E9818 /* LockConfiguration.swift in Sources */,
941957
6EF1C6D722C9AC9F005E9818 /* Permission.swift in Sources */,
@@ -987,6 +1003,7 @@
9871003
6EF1C6C422C9AC9F005E9818 /* Version.swift in Sources */,
9881004
6EE1D6E42357C639004DD856 /* KeysResponse.swift in Sources */,
9891005
6EE1D7A2235801BF004DD856 /* EventStore.swift in Sources */,
1006+
6EBDB5D3235D7FF900F38CB0 /* EventsResponse.swift in Sources */,
9901007
6EACDA9C231B353A000CF82A /* Event.swift in Sources */,
9911008
6E93D35F231C624300119F65 /* EventsCharacteristic.swift in Sources */,
9921009
6EF1C66022C9AC9F005E9818 /* EncryptedData.swift in Sources */,
@@ -999,6 +1016,7 @@
9991016
6EF1C66822C9AC9F005E9818 /* AuthenticationError.swift in Sources */,
10001017
6EF1C66C22C9AC9F005E9818 /* ListKeysCharacteristic.swift in Sources */,
10011018
6E93D357231C624300119F65 /* Notification.swift in Sources */,
1019+
6EBDB5CE235D5E3200F38CB0 /* EventsRequest.swift in Sources */,
10021020
6EF1C6BC22C9AC9F005E9818 /* UnlockCharacteristic.swift in Sources */,
10031021
6EF1C65C22C9AC9F005E9818 /* LockConfiguration.swift in Sources */,
10041022
6EF1C6D822C9AC9F005E9818 /* Permission.swift in Sources */,
@@ -1050,6 +1068,7 @@
10501068
6EF1C6C222C9AC9F005E9818 /* Version.swift in Sources */,
10511069
6EE1D6E22357C639004DD856 /* KeysResponse.swift in Sources */,
10521070
6EE1D7A0235801BF004DD856 /* EventStore.swift in Sources */,
1071+
6EBDB5D1235D7FF900F38CB0 /* EventsResponse.swift in Sources */,
10531072
6EACDA9A231B353A000CF82A /* Event.swift in Sources */,
10541073
6E93D35D231C624300119F65 /* EventsCharacteristic.swift in Sources */,
10551074
6EF1C65E22C9AC9F005E9818 /* EncryptedData.swift in Sources */,
@@ -1062,6 +1081,7 @@
10621081
6EF1C66622C9AC9F005E9818 /* AuthenticationError.swift in Sources */,
10631082
6EF1C66A22C9AC9F005E9818 /* ListKeysCharacteristic.swift in Sources */,
10641083
6E93D355231C624300119F65 /* Notification.swift in Sources */,
1084+
6EBDB5CC235D5E3200F38CB0 /* EventsRequest.swift in Sources */,
10651085
6EF1C6BA22C9AC9F005E9818 /* UnlockCharacteristic.swift in Sources */,
10661086
6EF1C65A22C9AC9F005E9818 /* LockConfiguration.swift in Sources */,
10671087
6EF1C6D622C9AC9F005E9818 /* Permission.swift in Sources */,

0 commit comments

Comments
 (0)