1
- import Foundation
2
1
import AsyncHTTPClient
2
+ import Foundation
3
3
import NIO
4
4
import NIOCore
5
- import NIOHTTP1
6
5
import NIOFoundationCompat
6
+ import NIOHTTP1
7
7
8
8
extension HTTPClientResponse {
9
9
var isOk : Bool {
@@ -14,11 +14,11 @@ extension HTTPClientResponse {
14
14
/// IndiePitcher SDK.
15
15
/// This SDK is only intended for server-side Swift use. Do not embed the secret API key in client-side code for security reasons.
16
16
public struct IndiePitcher : Sendable {
17
- private let client : HTTPClient // is sendable / thread-safe
17
+ private let client : HTTPClient // is sendable / thread-safe
18
18
private let apiKey : String
19
19
private let requestTimeout : TimeAmount = . seconds( 30 )
20
20
private let maxResponseSize = 1024 * 1024 * 100
21
-
21
+
22
22
/// Creates a new instance of IndiePitcher SDK
23
23
/// - Parameters:
24
24
/// - client: Vapor's client instance to use to perform network requests. Uses the shared client by default.
@@ -27,196 +27,235 @@ public struct IndiePitcher: Sendable {
27
27
self . client = client
28
28
self . apiKey = apiKey
29
29
}
30
-
30
+
31
31
// MARK: networking
32
-
32
+
33
33
private var commonHeaders : HTTPHeaders {
34
- get {
35
- var headers = HTTPHeaders ( )
36
- headers. add ( name: " Authorization " , value: " Bearer \( apiKey) " )
37
- headers. add ( name: " User-Agent " , value: " IndiePitcherSwift " )
38
- return headers
39
- }
34
+ var headers = HTTPHeaders ( )
35
+ headers. add ( name: " Authorization " , value: " Bearer \( apiKey) " )
36
+ headers. add ( name: " User-Agent " , value: " IndiePitcherSwift " )
37
+ return headers
40
38
}
41
-
39
+
42
40
private var jsonEncoder : JSONEncoder {
43
41
let encoder = JSONEncoder ( )
44
42
encoder. dateEncodingStrategy = . iso8601
45
43
return encoder
46
44
}
47
-
45
+
48
46
private var jsonDecoder : JSONDecoder {
49
47
let decoder = JSONDecoder ( )
50
48
decoder. dateDecodingStrategy = . iso8601
51
49
return decoder
52
50
}
53
-
51
+
54
52
private func buildUri( path: String ) -> String {
55
53
" https://api.indiepitcher.com/v1 " + path
56
54
}
57
-
58
- private func post< T: Codable > ( path: String , body: Codable ) async throws -> T {
59
-
55
+
56
+ private func post< T: Codable > ( path: String , body: Codable ) async throws -> T
57
+ {
58
+
60
59
var headers = commonHeaders
61
60
headers. add ( name: " Content-Type " , value: " application/json " )
62
-
61
+
63
62
var request = HTTPClientRequest ( url: buildUri ( path: path) )
64
63
request. method = . POST
65
64
request. headers = headers
66
65
request. body = . bytes( . init( data: try jsonEncoder. encode ( body) ) )
67
-
68
- let response = try await client. execute ( request, timeout: requestTimeout)
69
- let responseData = try await response. body. collect ( upTo: maxResponseSize)
70
-
66
+
67
+ let response = try await client. execute (
68
+ request, timeout: requestTimeout)
69
+ let responseData = try await response. body. collect (
70
+ upTo: maxResponseSize)
71
+
71
72
guard response. isOk else {
72
- let error = try ? jsonDecoder. decode ( ErrorResponse . self, from: responseData)
73
- throw IndiePitcherRequestError ( statusCode: response. status. code, reason: error? . reason ?? " Unknown reason " )
73
+ let error = try ? jsonDecoder. decode (
74
+ ErrorResponse . self, from: responseData)
75
+ throw IndiePitcherRequestError (
76
+ statusCode: response. status. code,
77
+ reason: error? . reason ?? " Unknown reason " )
74
78
}
75
-
79
+
76
80
return try self . jsonDecoder. decode ( T . self, from: responseData)
77
81
}
78
-
79
- private func patch< T: Codable > ( path: String , body: Codable ) async throws -> T {
80
-
82
+
83
+ private func patch< T: Codable > ( path: String , body: Codable ) async throws
84
+ -> T
85
+ {
86
+
81
87
var headers = commonHeaders
82
88
headers. add ( name: " Content-Type " , value: " application/json " )
83
-
89
+
84
90
var request = HTTPClientRequest ( url: buildUri ( path: path) )
85
91
request. method = . PATCH
86
92
request. headers = headers
87
93
request. body = . bytes( . init( data: try jsonEncoder. encode ( body) ) )
88
-
89
- let response = try await client. execute ( request, timeout: requestTimeout)
90
- let responseData = try await response. body. collect ( upTo: maxResponseSize)
91
-
94
+
95
+ let response = try await client. execute (
96
+ request, timeout: requestTimeout)
97
+ let responseData = try await response. body. collect (
98
+ upTo: maxResponseSize)
99
+
92
100
guard response. isOk else {
93
- let error = try ? jsonDecoder. decode ( ErrorResponse . self, from: responseData)
94
- throw IndiePitcherRequestError ( statusCode: response. status. code, reason: error? . reason ?? " Unknown reason " )
101
+ let error = try ? jsonDecoder. decode (
102
+ ErrorResponse . self, from: responseData)
103
+ throw IndiePitcherRequestError (
104
+ statusCode: response. status. code,
105
+ reason: error? . reason ?? " Unknown reason " )
95
106
}
96
-
107
+
97
108
return try self . jsonDecoder. decode ( T . self, from: responseData)
98
109
}
99
-
110
+
100
111
private func get< T: Codable > ( path: String ) async throws -> T {
101
-
112
+
102
113
let headers = commonHeaders
103
-
114
+
104
115
var request = HTTPClientRequest ( url: buildUri ( path: path) )
105
116
request. method = . GET
106
117
request. headers = headers
107
-
108
- let response = try await client. execute ( request, timeout: requestTimeout)
109
- let responseData = try await response. body. collect ( upTo: maxResponseSize)
110
-
118
+
119
+ let response = try await client. execute (
120
+ request, timeout: requestTimeout)
121
+ let responseData = try await response. body. collect (
122
+ upTo: maxResponseSize)
123
+
111
124
guard response. isOk else {
112
- let error = try ? jsonDecoder. decode ( ErrorResponse . self, from: responseData)
113
- throw IndiePitcherRequestError ( statusCode: response. status. code, reason: error? . reason ?? " Unknown reason " )
125
+ let error = try ? jsonDecoder. decode (
126
+ ErrorResponse . self, from: responseData)
127
+ throw IndiePitcherRequestError (
128
+ statusCode: response. status. code,
129
+ reason: error? . reason ?? " Unknown reason " )
114
130
}
115
-
131
+
116
132
return try self . jsonDecoder. decode ( T . self, from: responseData)
117
133
}
118
-
134
+
119
135
// MARK: API calls
120
-
136
+
121
137
/// Add a new contact to the mailing list, or update an existing one if `updateIfExists` is set to `true`.
122
138
/// - Parameter contact: Contact properties.
123
139
/// - Returns: Created contact.
124
- @discardableResult public func addContact( contact: CreateContact ) async throws -> DataResponse < Contact > {
140
+ @discardableResult public func addContact( contact: CreateContact )
141
+ async throws -> DataResponse < Contact >
142
+ {
125
143
try await post ( path: " /contacts/create " , body: contact)
126
144
}
127
-
145
+
128
146
/// Add miultiple contacts (up to 100) using a single API call to avoid being rate limited. Payloads with `updateIfExists` is set to `true` will be updated if a contact with given email already exists.
129
147
/// - Parameter contacts: Contact properties
130
148
/// - Returns: A generic empty response.
131
- @discardableResult public func addContacts( contacts: [ CreateContact ] ) async throws -> EmptyResposne {
132
-
149
+ @discardableResult public func addContacts( contacts: [ CreateContact ] )
150
+ async throws -> EmptyResposne
151
+ {
152
+
133
153
struct Payload : Codable {
134
154
let contacts : [ CreateContact ]
135
155
}
136
-
137
- return try await post ( path: " /contacts/create_many " , body: Payload ( contacts: contacts) )
156
+
157
+ return try await post (
158
+ path: " /contacts/create_many " , body: Payload ( contacts: contacts) )
138
159
}
139
-
160
+
140
161
/// Updates a contact with given email address. This call will fail if a contact with provided email does not exist, use `addContact` instead in such case.
141
162
/// - Parameter contact: Contact properties to update
142
163
/// - Returns: Updated contact.
143
- @discardableResult public func updateContact( contact: UpdateContact ) async throws -> DataResponse < Contact > {
164
+ @discardableResult public func updateContact( contact: UpdateContact )
165
+ async throws -> DataResponse < Contact >
166
+ {
144
167
try await patch ( path: " /contacts/update " , body: contact)
145
168
}
146
-
169
+
147
170
/// Deletes a contact with provided email from the mailing list
148
171
/// - Parameter email: The email address of the contact you wish to remove from the mailing list
149
172
/// - Returns: A generic empty response.
150
- @discardableResult public func deleteContact( email: String ) async throws -> EmptyResposne {
151
-
173
+ @discardableResult public func deleteContact( email: String ) async throws
174
+ -> EmptyResposne
175
+ {
176
+
152
177
struct Payload : Codable {
153
178
var email : String
154
179
}
155
-
156
- return try await post ( path: " /contacts/delete " , body: Payload ( email: email) )
180
+
181
+ return try await post (
182
+ path: " /contacts/delete " , body: Payload ( email: email) )
157
183
}
158
-
184
+
159
185
/// Returns a paginated list of stored contacts in the mailing list.
160
186
/// - Parameters:
161
187
/// - page: Page to fetch, the first page has index 1.
162
188
/// - perPage: How many contacts to return per page.
163
189
/// - Returns: A paginated array of contacts
164
- public func listContacts( page: Int = 1 , perPage: Int = 10 ) async throws -> PagedDataResponse < Contact > {
190
+ public func listContacts( page: Int = 1 , perPage: Int = 10 ) async throws
191
+ -> PagedDataResponse < Contact >
192
+ {
165
193
try await get ( path: " /contacts?page= \( page) &per= \( perPage) " )
166
194
}
167
-
195
+
168
196
/// Sends an email to specified email address.
169
197
/// The email is not required to belong to a contact in your contact lsit. Use this API to send emails such as that a user who is not signed up for your product was invited to a team.
170
198
/// - Parameter data: Input params.
171
199
/// - Returns: A genereic response with no return data.
172
- @discardableResult public func sendEmail( data: SendEmail ) async throws -> EmptyResposne {
200
+ @discardableResult public func sendEmail( data: SendEmail ) async throws
201
+ -> EmptyResposne
202
+ {
173
203
try await post ( path: " /email/transactional " , body: data)
174
204
}
175
-
205
+
176
206
/// Send a personalized email to one more (up to 100 using 1 API call) contacts subscribed to a proviced mailing list. This is the recommended way to send an email to members of a team of your product.
177
207
/// All provided emails must belong to your mailing list and must be members of provided mailing list. All contacts are automatically subscribed to `important` default mailing list. You can use peronalization tags such as `Hi {{firstName}}` to peronalize individual sent emails, and scheduled it to be sent with a delay.
178
208
/// - Parameter data: Input params.
179
209
/// - Returns: A genereic response with no return data.
180
- @discardableResult public func sendEmailToContact( data: SendEmailToContact ) async throws -> EmptyResposne {
210
+ @discardableResult public func sendEmailToContact( data: SendEmailToContact )
211
+ async throws -> EmptyResposne
212
+ {
181
213
try await post ( path: " /email/contact " , body: data)
182
214
}
183
-
215
+
184
216
/// Send a personalized email to all contacts subscribed to a provided mailing list. This is the recommendat way to send a newsletter, by creating a list called something like `Newsletter`.
185
217
/// All contacts are automatically subscribed to `important` default mailing list. You can use peronalization tags such as `Hi {{firstName}}` to peronalize individual sent emails, and scheduled it to be sent with a delay.
186
218
/// - Parameter data: Input params.
187
219
/// - Returns: A genereic response with no return data.
188
- @discardableResult public func sendEmailToMailingList( data: SendEmailToMailingList ) async throws -> EmptyResposne {
220
+ @discardableResult public func sendEmailToMailingList(
221
+ data: SendEmailToMailingList
222
+ ) async throws -> EmptyResposne {
189
223
try await post ( path: " /email/list " , body: data)
190
224
}
191
-
225
+
192
226
/// Returns mailing lists contacts can subscribe to.
193
227
/// - Parameters:
194
228
/// - page: Page to fetch, the first page has index 1.
195
229
/// - perPage: How many contacts to return per page.
196
230
/// - Returns: A paginated array of mailing lists
197
- public func listMailingLists( page: Int = 1 , perPage: Int = 10 ) async throws -> PagedDataResponse < MailingList > {
198
-
231
+ public func listMailingLists( page: Int = 1 , perPage: Int = 10 ) async throws
232
+ -> PagedDataResponse < MailingList >
233
+ {
234
+
199
235
struct Payload : Codable {
200
236
let page : Int
201
237
let per : Int
202
238
}
203
-
239
+
204
240
return try await get ( path: " /lists?page= \( page) &per= \( perPage) " )
205
241
}
206
-
207
-
242
+
208
243
/// Generates a new public URL for a contact with provided email to manage their mailing list subscriptions.
209
244
/// - Parameters:
210
245
/// - contactEmail: The email of a contact in your project's contact list, who to create the portal session for.
211
246
/// - returnURL: The URL to redirect to when the user is done editing their mailing list, or when the session has expired.
212
247
/// - Returns: The URL to redirect your user to, and the expiration date of the session.
213
- public func createMailingListsPortalSession( contactEmail: String , returnURL: URL ) async throws -> DataResponse < MailingListPortalSession > {
214
-
248
+ public func createMailingListsPortalSession(
249
+ contactEmail: String , returnURL: URL
250
+ ) async throws -> DataResponse < MailingListPortalSession > {
251
+
215
252
struct Payload : Codable {
216
253
let contactEmail : String
217
254
let returnURL : URL
218
255
}
219
-
220
- return try await post ( path: " /lists/portal_session " , body: Payload ( contactEmail: contactEmail, returnURL: returnURL) )
256
+
257
+ return try await post (
258
+ path: " /lists/portal_session " ,
259
+ body: Payload ( contactEmail: contactEmail, returnURL: returnURL) )
221
260
}
222
261
}
0 commit comments