forked from parse-community/Parse-Swift
-
-
Notifications
You must be signed in to change notification settings - Fork 7
/
Contents.swift
433 lines (381 loc) · 13.2 KB
/
Contents.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
//: [Previous](@previous)
//: For this page, make sure your build target is set to ParseSwift and targeting
//: `My Mac` or whatever the name of your mac is. Also be sure your `Playground Settings`
//: in the `File Inspector` is `Platform = macOS`. This is because
//: Keychain in iOS Playgrounds behaves differently. Every page in Playgrounds should
//: be set to build for `macOS` unless specified.
import PlaygroundSupport
import Foundation
import ParseSwift
PlaygroundPage.current.needsIndefiniteExecution = true
Task {
do {
try await initializeParse()
} catch {
assertionFailure("Error initializing Parse-Swift: \(error)")
}
}
struct User: ParseUser {
//: These are required by `ParseObject`.
var objectId: String?
var createdAt: Date?
var updatedAt: Date?
var ACL: ParseACL?
var originalData: Data?
//: These are required by `ParseUser`.
var username: String?
var email: String?
var emailVerified: Bool?
var password: String?
var authData: [String: [String: String]?]?
//: Your custom keys.
var customKey: String?
var scores: ParseRelation<Self>?
/*:
Optional - implement your own version of merge
for faster decoding after updating your `ParseObject`.
*/
func merge(with object: Self) throws -> Self {
var updated = try mergeParse(with: object)
if updated.shouldRestoreKey(\.customKey,
original: object) {
updated.customKey = object.customKey
}
return updated
}
}
struct Role<RoleUser: ParseUser>: ParseRole {
//: Required by `ParseObject`.
var objectId: String?
var createdAt: Date?
var updatedAt: Date?
var ACL: ParseACL?
var originalData: Data?
//: Provided by Role.
var name: String?
//: Custom properties.
var subtitle: String?
/*:
Optional - implement your own version of merge
for faster decoding after updating your `ParseObject`.
*/
func merge(with object: Self) throws -> Self {
var updated = try mergeParse(with: object)
if updated.shouldRestoreKey(\.subtitle,
original: object) {
updated.subtitle = object.subtitle
}
return updated
}
}
//: Create your own value typed `ParseObject`.
struct GameScore: ParseObject {
//: These are required by ParseObject
var objectId: String?
var createdAt: Date?
var updatedAt: Date?
var ACL: ParseACL?
var originalData: Data?
//: Your own properties.
var points: Int?
/*:
Optional - implement your own version of merge
for faster decoding after updating your `ParseObject`.
*/
func merge(with object: Self) throws -> Self {
var updated = try mergeParse(with: object)
if updated.shouldRestoreKey(\.points,
original: object) {
updated.points = object.points
}
return updated
}
}
//: It's recommended to place custom initializers in an extension
//: to preserve the memberwise initializer.
extension GameScore {
init(points: Int) {
self.points = points
}
}
//: Roles can provide additional access/security to your apps.
//: This variable will store the saved role.
var savedRole: Role<User>?
Task {
//: Now we will create the Role.
do {
let currentUser = try await User.current()
//: Every Role requires an ACL that cannot be changed after saving.
var acl = ParseACL()
acl.setReadAccess(user: currentUser, value: true)
acl.setWriteAccess(user: currentUser, value: true)
do {
//: Create the actual Role with a name and ACL.
var adminRole = try Role<User>(name: "Administrator", acl: acl)
adminRole.subtitle = "staff"
adminRole.save { result in
switch result {
case .success(let saved):
print("The role saved successfully: \(saved)")
print("Check your \"Role\" class in Parse Dashboard.")
//: Store the saved role so we can use it later...
savedRole = saved
case .failure(let error):
print("Error saving role: \(error)")
}
}
} catch {
print("Error: \(error)")
}
} catch {
fatalError("User currently is not signed in")
}
}
//: Lets check to see if our Role has saved.
if savedRole != nil {
print("We have a saved Role")
}
Task {
//: Users can be added to our previously saved Role.
do {
//: `ParseRoles` have `ParseRelations` that relate them either `ParseUser` and `ParseRole` objects.
//: The `ParseUser` relations can be accessed using `users`. We can then add `ParseUser`'s to the relation.
let currentUser = try await User.current()
try savedRole!.users?.add([currentUser]).save { result in
switch result {
case .success(let saved):
print("The role saved successfully: \(saved)")
print("Check \"users\" field in your \"Role\" class in Parse Dashboard.")
case .failure(let error):
print("Error saving role: \(error)")
}
}
} catch {
print("Error: \(error)")
}
}
/*:
To retrieve the users who are all Administrators,
we need to query the relation.
*/
do {
let query: Query<User>? = try savedRole!.users?.query()
query?.find { result in
switch result {
case .success(let relatedUsers):
print("""
The following users are part of the
\"\(String(describing: savedRole!.name)) role: \(relatedUsers)
""")
case .failure(let error):
print("Error querying role: \(error)")
}
}
} catch {
print(error)
}
Task {
//: Of course, you can remove users from the roles as well.
do {
let currentUser = try await User.current()
try savedRole!.users?.remove([currentUser]).save { result in
switch result {
case .success(let saved):
print("The role removed successfully: \(saved)")
print("Check \"users\" field in your \"Role\" class in Parse Dashboard.")
case .failure(let error):
print("Error saving role: \(error)")
}
}
} catch {
print(error)
}
}
/*:
Additional roles can be created and tied to already created roles.
Lets create a "Member" role.
*/
//: This variable will store the saved role.
var savedRoleModerator: Role<User>?
Task {
do {
let currentUser = try await User.current()
var acl = ParseACL()
acl.setReadAccess(user: currentUser, value: true)
acl.setWriteAccess(user: currentUser, value: true)
//: Create the actual Role with a name and ACL.
let memberRole = try Role<User>(name: "Member", acl: acl)
memberRole.save { result in
switch result {
case .success(let saved):
print("The role saved successfully: \(saved)")
print("Check your \"Role\" class in Parse Dashboard.")
//: Store the saved role so we can use it later...
savedRoleModerator = saved
case .failure(let error):
print("Error saving role: \(error)")
}
}
} catch {
print("Error: \(error)")
}
}
//: Lets check to see if our Role has saved
if savedRoleModerator != nil {
print("We have a saved Role")
}
//: Roles can be added to our previously saved Role.
do {
/*:
`ParseRoles` have `ParseRelations` that relate them either
`ParseUser` and `ParseRole` objects. The `ParseUser`
relations can be accessed using `users`. We can then add
`ParseUser`'s to the relation.
*/
try savedRole!.roles?.add([savedRoleModerator!]).save { result in
switch result {
case .success(let saved):
print("The role saved successfully: \(saved)")
print("Check \"roles\" field in your \"Role\" class in Parse Dashboard.")
case .failure(let error):
print("Error saving role: \(error)")
}
}
} catch {
print("Error: \(error)")
}
/*:
To retrieve the users who are all Administrators,
we need to query the relation. This time we will
use a helper query from `ParseRole`.
*/
do {
try savedRole!.queryRoles().find { result in
switch result {
case .success(let relatedRoles):
print("""
The following roles are part of the
\"\(String(describing: savedRole!.name)) role: \(relatedRoles)
""")
case .failure(let error):
print("Error querying role: \(error)")
}
}
} catch {
print("Error: \(error)")
}
//: Of course, you can remove users from the roles as well.
do {
try savedRole!.roles?.remove([savedRoleModerator!]).save { result in
switch result {
case .success(let saved):
print("The role removed successfully: \(saved)")
print("Check the \"roles\" field in your \"Role\" class in Parse Dashboard.")
case .failure(let error):
print("Error saving role: \(error)")
}
}
} catch {
print(error)
}
Task {
do {
let currentUser = try await User.current()
/*:
Using this relation, you can create one-to-many relationships
with other `ParseObjecs`, similar to `users` and `roles`. All
`ParseObject`s have a `ParseRelation` attribute that be used on
instances. For example, the User has:
*/
var relation = currentUser.relation
let score1 = GameScore(points: 53)
let score2 = GameScore(points: 57)
//: Add new child relationships.
[score1, score2].saveAll { result in
switch result {
case .success(let savedScores):
//: Make an array of all scores that were properly saved.
let scores = savedScores.compactMap { try? $0.get() }
do {
guard let newRelations = try relation?.add("scores", objects: scores) else {
print("Error: should have unwrapped relation")
return
}
newRelations.save { result in
switch result {
case .success(let saved):
print("The relation saved successfully: \(saved)")
print("Check \"points\" field in your \"_User\" class in Parse Dashboard.")
case .failure(let error):
print("Error saving role: \(error)")
}
}
} catch {
print(error)
}
case .failure(let error):
print("Could not save scores. \(error)")
}
}
} catch {
assertionFailure("Error: \(error)")
}
}
Task {
let score1 = GameScore(points: 53)
//: You can also do
// let specificRelation = try await User.current().relation("scores", object: GameScore.self)
do {
let currentUser = try await User.current()
let specificRelation = try currentUser.relation("scores", child: score1)
try (specificRelation.query() as Query<GameScore>).find { result in
switch result {
case .success(let scores):
print("Found related scores: \(scores)")
case .failure(let error):
print("Error querying scores: \(error)")
}
}
} catch {
print(error)
}
}
Task {
//: In addition, you can leverage the child to find scores related to the parent.
do {
let currentUser = try await User.current()
try GameScore.queryRelations("scores", parent: currentUser).find { result in
switch result {
case .success(let scores):
print("Found related scores from child: \(scores)")
case .failure(let error):
print("Error querying scores from child: \(error)")
}
}
} catch {
print(error)
}
}
Task {
do {
//: Now we will see how to use the stored `ParseRelation on` property in User to create query
//: all of the relations to `scores`.
var currentUser = try await User.current()
//: Fetch the updated user since the previous relations were created on the server.
currentUser = try await currentUser.fetch()
print("Updated current user with relation: \(currentUser)")
let usableStoredRelation = try currentUser.relation(currentUser.scores, key: "scores")
try (usableStoredRelation.query() as Query<GameScore>).find { result in
switch result {
case .success(let scores):
print("Found related scores from stored ParseRelation: \(scores)")
case .failure(let error):
print("Error querying scores from stored ParseRelation: \(error)")
}
}
} catch {
print("\(error.localizedDescription)")
}
}
PlaygroundPage.current.finishExecution()
//: [Next](@next)