@prmichaelsen/firebase-admin-sdk-v8 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/EXAMPLES.md +532 -0
- package/README.md +495 -0
- package/dist/index.d.mts +473 -0
- package/dist/index.d.ts +473 -0
- package/dist/index.js +714 -0
- package/dist/index.mjs +673 -0
- package/package.json +40 -0
package/EXAMPLES.md
ADDED
|
@@ -0,0 +1,532 @@
|
|
|
1
|
+
# Firebase Admin SDK v8 - Advanced Examples
|
|
2
|
+
|
|
3
|
+
This document provides comprehensive examples of all features in the Firebase Admin SDK v8.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Authentication](#authentication)
|
|
8
|
+
- [Basic Firestore Operations](#basic-firestore-operations)
|
|
9
|
+
- [Field Value Operations](#field-value-operations)
|
|
10
|
+
- [Advanced Queries](#advanced-queries)
|
|
11
|
+
- [Batch Operations](#batch-operations)
|
|
12
|
+
- [Merge Operations](#merge-operations)
|
|
13
|
+
|
|
14
|
+
## Authentication
|
|
15
|
+
|
|
16
|
+
### Verify ID Token
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
import { verifyIdToken, getUserFromToken } from 'firebase-admin-sdk-v8';
|
|
20
|
+
|
|
21
|
+
// Verify token and get decoded claims
|
|
22
|
+
const decodedToken = await verifyIdToken(idToken);
|
|
23
|
+
console.log('User ID:', decodedToken.uid);
|
|
24
|
+
console.log('Email:', decodedToken.email);
|
|
25
|
+
|
|
26
|
+
// Get user info in a convenient format
|
|
27
|
+
const user = await getUserFromToken(idToken);
|
|
28
|
+
console.log('User:', user.uid, user.email, user.displayName);
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Basic Firestore Operations
|
|
32
|
+
|
|
33
|
+
### Set Document (Create or Overwrite)
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
import { setDocument } from 'firebase-admin-sdk-v8';
|
|
37
|
+
|
|
38
|
+
// Create or completely overwrite a document
|
|
39
|
+
await setDocument('users', 'user123', {
|
|
40
|
+
name: 'John Doe',
|
|
41
|
+
email: 'john@example.com',
|
|
42
|
+
age: 30,
|
|
43
|
+
active: true,
|
|
44
|
+
});
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Add Document (Auto-generate ID)
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
import { addDocument } from 'firebase-admin-sdk-v8';
|
|
51
|
+
|
|
52
|
+
// Auto-generate document ID
|
|
53
|
+
const docId = await addDocument('posts', {
|
|
54
|
+
title: 'Hello World',
|
|
55
|
+
content: 'This is my first post',
|
|
56
|
+
createdAt: new Date(),
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// Or specify custom ID
|
|
60
|
+
const customId = await addDocument('posts', {
|
|
61
|
+
title: 'Custom ID Post',
|
|
62
|
+
}, 'my-custom-id');
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Get Document
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
import { getDocument } from 'firebase-admin-sdk-v8';
|
|
69
|
+
|
|
70
|
+
const user = await getDocument('users', 'user123');
|
|
71
|
+
if (user) {
|
|
72
|
+
console.log('User found:', user.name);
|
|
73
|
+
} else {
|
|
74
|
+
console.log('User not found');
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Update Document
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
import { updateDocument } from 'firebase-admin-sdk-v8';
|
|
82
|
+
|
|
83
|
+
// Update specific fields (document must exist)
|
|
84
|
+
await updateDocument('users', 'user123', {
|
|
85
|
+
lastLogin: new Date(),
|
|
86
|
+
loginCount: 5,
|
|
87
|
+
});
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Delete Document
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
import { deleteDocument } from 'firebase-admin-sdk-v8';
|
|
94
|
+
|
|
95
|
+
await deleteDocument('users', 'user123');
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Field Value Operations
|
|
99
|
+
|
|
100
|
+
### Server Timestamp
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
import { setDocument, FieldValue } from 'firebase-admin-sdk-v8';
|
|
104
|
+
|
|
105
|
+
await setDocument('posts', 'post1', {
|
|
106
|
+
title: 'My Post',
|
|
107
|
+
createdAt: FieldValue.serverTimestamp(),
|
|
108
|
+
updatedAt: FieldValue.serverTimestamp(),
|
|
109
|
+
});
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Increment
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
import { updateDocument, FieldValue } from 'firebase-admin-sdk-v8';
|
|
116
|
+
|
|
117
|
+
// Increment a counter
|
|
118
|
+
await updateDocument('users', 'user123', {
|
|
119
|
+
loginCount: FieldValue.increment(1),
|
|
120
|
+
points: FieldValue.increment(10),
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// Decrement (use negative number)
|
|
124
|
+
await updateDocument('inventory', 'item1', {
|
|
125
|
+
stock: FieldValue.increment(-1),
|
|
126
|
+
});
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Array Union (Add to Array)
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
import { updateDocument, FieldValue } from 'firebase-admin-sdk-v8';
|
|
133
|
+
|
|
134
|
+
// Add tags to array (no duplicates)
|
|
135
|
+
await updateDocument('posts', 'post1', {
|
|
136
|
+
tags: FieldValue.arrayUnion('javascript', 'typescript', 'firebase'),
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// Add multiple items
|
|
140
|
+
await updateDocument('users', 'user123', {
|
|
141
|
+
favoriteColors: FieldValue.arrayUnion('blue', 'green'),
|
|
142
|
+
});
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Array Remove
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
import { updateDocument, FieldValue } from 'firebase-admin-sdk-v8';
|
|
149
|
+
|
|
150
|
+
// Remove tags from array
|
|
151
|
+
await updateDocument('posts', 'post1', {
|
|
152
|
+
tags: FieldValue.arrayRemove('outdated-tag'),
|
|
153
|
+
});
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Delete Field
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
import { updateDocument, FieldValue } from 'firebase-admin-sdk-v8';
|
|
160
|
+
|
|
161
|
+
// Remove a field from document
|
|
162
|
+
await updateDocument('users', 'user123', {
|
|
163
|
+
temporaryField: FieldValue.delete(),
|
|
164
|
+
oldField: FieldValue.delete(),
|
|
165
|
+
});
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Advanced Queries
|
|
169
|
+
|
|
170
|
+
### Simple Query
|
|
171
|
+
|
|
172
|
+
```typescript
|
|
173
|
+
import { queryDocuments } from 'firebase-admin-sdk-v8';
|
|
174
|
+
|
|
175
|
+
// Get all documents in collection
|
|
176
|
+
const allUsers = await queryDocuments('users');
|
|
177
|
+
allUsers.forEach(user => {
|
|
178
|
+
console.log(user.id, user.data.name);
|
|
179
|
+
});
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Where Filters
|
|
183
|
+
|
|
184
|
+
```typescript
|
|
185
|
+
import { queryDocuments } from 'firebase-admin-sdk-v8';
|
|
186
|
+
|
|
187
|
+
// Single condition
|
|
188
|
+
const activeUsers = await queryDocuments('users', {
|
|
189
|
+
where: [
|
|
190
|
+
{ field: 'active', op: '==', value: true }
|
|
191
|
+
]
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
// Multiple conditions (AND)
|
|
195
|
+
const adultActiveUsers = await queryDocuments('users', {
|
|
196
|
+
where: [
|
|
197
|
+
{ field: 'active', op: '==', value: true },
|
|
198
|
+
{ field: 'age', op: '>=', value: 18 }
|
|
199
|
+
]
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
// Comparison operators
|
|
203
|
+
const seniorUsers = await queryDocuments('users', {
|
|
204
|
+
where: [
|
|
205
|
+
{ field: 'age', op: '>', value: 65 }
|
|
206
|
+
]
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
// Array contains
|
|
210
|
+
const jsDevs = await queryDocuments('users', {
|
|
211
|
+
where: [
|
|
212
|
+
{ field: 'skills', op: 'array-contains', value: 'javascript' }
|
|
213
|
+
]
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
// In operator
|
|
217
|
+
const specificUsers = await queryDocuments('users', {
|
|
218
|
+
where: [
|
|
219
|
+
{ field: 'role', op: 'in', value: ['admin', 'moderator'] }
|
|
220
|
+
]
|
|
221
|
+
});
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### Order By
|
|
225
|
+
|
|
226
|
+
```typescript
|
|
227
|
+
import { queryDocuments } from 'firebase-admin-sdk-v8';
|
|
228
|
+
|
|
229
|
+
// Order by single field
|
|
230
|
+
const usersByName = await queryDocuments('users', {
|
|
231
|
+
orderBy: [
|
|
232
|
+
{ field: 'name', direction: 'ASCENDING' }
|
|
233
|
+
]
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
// Order by multiple fields
|
|
237
|
+
const sortedPosts = await queryDocuments('posts', {
|
|
238
|
+
orderBy: [
|
|
239
|
+
{ field: 'featured', direction: 'DESCENDING' },
|
|
240
|
+
{ field: 'createdAt', direction: 'DESCENDING' }
|
|
241
|
+
]
|
|
242
|
+
});
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### Limit and Offset
|
|
246
|
+
|
|
247
|
+
```typescript
|
|
248
|
+
import { queryDocuments } from 'firebase-admin-sdk-v8';
|
|
249
|
+
|
|
250
|
+
// Get first 10 users
|
|
251
|
+
const firstPage = await queryDocuments('users', {
|
|
252
|
+
limit: 10
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
// Pagination with offset
|
|
256
|
+
const secondPage = await queryDocuments('users', {
|
|
257
|
+
limit: 10,
|
|
258
|
+
offset: 10
|
|
259
|
+
});
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### Complex Query
|
|
263
|
+
|
|
264
|
+
```typescript
|
|
265
|
+
import { queryDocuments } from 'firebase-admin-sdk-v8';
|
|
266
|
+
|
|
267
|
+
// Combine filters, ordering, and limit
|
|
268
|
+
const topActiveUsers = await queryDocuments('users', {
|
|
269
|
+
where: [
|
|
270
|
+
{ field: 'active', op: '==', value: true },
|
|
271
|
+
{ field: 'points', op: '>=', value: 100 }
|
|
272
|
+
],
|
|
273
|
+
orderBy: [
|
|
274
|
+
{ field: 'points', direction: 'DESCENDING' }
|
|
275
|
+
],
|
|
276
|
+
limit: 10
|
|
277
|
+
});
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### Cursor-based Pagination
|
|
281
|
+
|
|
282
|
+
```typescript
|
|
283
|
+
import { queryDocuments } from 'firebase-admin-sdk-v8';
|
|
284
|
+
|
|
285
|
+
// Start at specific value
|
|
286
|
+
const results = await queryDocuments('users', {
|
|
287
|
+
orderBy: [{ field: 'createdAt', direction: 'ASCENDING' }],
|
|
288
|
+
startAt: [new Date('2024-01-01')],
|
|
289
|
+
limit: 10
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
// Start after specific value (exclusive)
|
|
293
|
+
const nextResults = await queryDocuments('users', {
|
|
294
|
+
orderBy: [{ field: 'createdAt', direction: 'ASCENDING' }],
|
|
295
|
+
startAfter: [new Date('2024-01-01')],
|
|
296
|
+
limit: 10
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
// End at specific value
|
|
300
|
+
const endResults = await queryDocuments('users', {
|
|
301
|
+
orderBy: [{ field: 'createdAt', direction: 'ASCENDING' }],
|
|
302
|
+
endAt: [new Date('2024-12-31')],
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
// End before specific value (exclusive)
|
|
306
|
+
const beforeResults = await queryDocuments('users', {
|
|
307
|
+
orderBy: [{ field: 'createdAt', direction: 'ASCENDING' }],
|
|
308
|
+
endBefore: [new Date('2024-12-31')],
|
|
309
|
+
});
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
## Batch Operations
|
|
313
|
+
|
|
314
|
+
### Batch Write (Multiple Operations)
|
|
315
|
+
|
|
316
|
+
```typescript
|
|
317
|
+
import { batchWrite } from 'firebase-admin-sdk-v8';
|
|
318
|
+
|
|
319
|
+
// Perform multiple operations atomically
|
|
320
|
+
await batchWrite([
|
|
321
|
+
// Set a document
|
|
322
|
+
{
|
|
323
|
+
type: 'set',
|
|
324
|
+
collectionPath: 'users',
|
|
325
|
+
documentId: 'user1',
|
|
326
|
+
data: { name: 'John', email: 'john@example.com' }
|
|
327
|
+
},
|
|
328
|
+
|
|
329
|
+
// Update a document
|
|
330
|
+
{
|
|
331
|
+
type: 'update',
|
|
332
|
+
collectionPath: 'users',
|
|
333
|
+
documentId: 'user2',
|
|
334
|
+
data: { lastLogin: new Date() }
|
|
335
|
+
},
|
|
336
|
+
|
|
337
|
+
// Delete a document
|
|
338
|
+
{
|
|
339
|
+
type: 'delete',
|
|
340
|
+
collectionPath: 'users',
|
|
341
|
+
documentId: 'user3'
|
|
342
|
+
}
|
|
343
|
+
]);
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
### Batch with Field Values
|
|
347
|
+
|
|
348
|
+
```typescript
|
|
349
|
+
import { batchWrite, FieldValue } from 'firebase-admin-sdk-v8';
|
|
350
|
+
|
|
351
|
+
await batchWrite([
|
|
352
|
+
{
|
|
353
|
+
type: 'update',
|
|
354
|
+
collectionPath: 'posts',
|
|
355
|
+
documentId: 'post1',
|
|
356
|
+
data: {
|
|
357
|
+
views: FieldValue.increment(1),
|
|
358
|
+
lastViewed: FieldValue.serverTimestamp()
|
|
359
|
+
}
|
|
360
|
+
},
|
|
361
|
+
{
|
|
362
|
+
type: 'update',
|
|
363
|
+
collectionPath: 'users',
|
|
364
|
+
documentId: 'user1',
|
|
365
|
+
data: {
|
|
366
|
+
postsViewed: FieldValue.arrayUnion('post1')
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
]);
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
## Merge Operations
|
|
373
|
+
|
|
374
|
+
### Merge All Fields
|
|
375
|
+
|
|
376
|
+
```typescript
|
|
377
|
+
import { setDocument } from 'firebase-admin-sdk-v8';
|
|
378
|
+
|
|
379
|
+
// Existing document: { name: 'John', age: 30, city: 'NYC' }
|
|
380
|
+
|
|
381
|
+
// Merge new data with existing
|
|
382
|
+
await setDocument('users', 'user123', {
|
|
383
|
+
age: 31,
|
|
384
|
+
country: 'USA'
|
|
385
|
+
}, { merge: true });
|
|
386
|
+
|
|
387
|
+
// Result: { name: 'John', age: 31, city: 'NYC', country: 'USA' }
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
### Merge Specific Fields
|
|
391
|
+
|
|
392
|
+
```typescript
|
|
393
|
+
import { setDocument } from 'firebase-admin-sdk-v8';
|
|
394
|
+
|
|
395
|
+
// Existing document: { name: 'John', age: 30, city: 'NYC' }
|
|
396
|
+
|
|
397
|
+
// Only merge the 'age' field
|
|
398
|
+
await setDocument('users', 'user123', {
|
|
399
|
+
age: 31,
|
|
400
|
+
city: 'LA',
|
|
401
|
+
country: 'USA'
|
|
402
|
+
}, { mergeFields: ['age'] });
|
|
403
|
+
|
|
404
|
+
// Result: { name: 'John', age: 31, city: 'NYC' }
|
|
405
|
+
// Note: city and country were NOT updated
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
### Merge with Field Values
|
|
409
|
+
|
|
410
|
+
```typescript
|
|
411
|
+
import { setDocument, FieldValue } from 'firebase-admin-sdk-v8';
|
|
412
|
+
|
|
413
|
+
// Merge with special field values
|
|
414
|
+
await setDocument('users', 'user123', {
|
|
415
|
+
lastLogin: FieldValue.serverTimestamp(),
|
|
416
|
+
loginCount: FieldValue.increment(1),
|
|
417
|
+
devices: FieldValue.arrayUnion('mobile')
|
|
418
|
+
}, { merge: true });
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
## Real-World Examples
|
|
422
|
+
|
|
423
|
+
### User Registration
|
|
424
|
+
|
|
425
|
+
```typescript
|
|
426
|
+
import { addDocument, FieldValue } from 'firebase-admin-sdk-v8';
|
|
427
|
+
|
|
428
|
+
async function registerUser(email: string, name: string) {
|
|
429
|
+
const userId = await addDocument('users', {
|
|
430
|
+
email,
|
|
431
|
+
name,
|
|
432
|
+
createdAt: FieldValue.serverTimestamp(),
|
|
433
|
+
loginCount: 0,
|
|
434
|
+
active: true,
|
|
435
|
+
roles: ['user'],
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
return userId;
|
|
439
|
+
}
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
### Track User Activity
|
|
443
|
+
|
|
444
|
+
```typescript
|
|
445
|
+
import { updateDocument, FieldValue } from 'firebase-admin-sdk-v8';
|
|
446
|
+
|
|
447
|
+
async function trackUserLogin(userId: string) {
|
|
448
|
+
await updateDocument('users', userId, {
|
|
449
|
+
lastLogin: FieldValue.serverTimestamp(),
|
|
450
|
+
loginCount: FieldValue.increment(1),
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
### Blog Post with Tags
|
|
456
|
+
|
|
457
|
+
```typescript
|
|
458
|
+
import { addDocument, updateDocument, FieldValue } from 'firebase-admin-sdk-v8';
|
|
459
|
+
|
|
460
|
+
async function createPost(title: string, content: string, tags: string[]) {
|
|
461
|
+
const postId = await addDocument('posts', {
|
|
462
|
+
title,
|
|
463
|
+
content,
|
|
464
|
+
tags,
|
|
465
|
+
views: 0,
|
|
466
|
+
likes: 0,
|
|
467
|
+
createdAt: FieldValue.serverTimestamp(),
|
|
468
|
+
updatedAt: FieldValue.serverTimestamp(),
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
return postId;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
async function addTagToPost(postId: string, tag: string) {
|
|
475
|
+
await updateDocument('posts', postId, {
|
|
476
|
+
tags: FieldValue.arrayUnion(tag),
|
|
477
|
+
updatedAt: FieldValue.serverTimestamp(),
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
async function incrementPostViews(postId: string) {
|
|
482
|
+
await updateDocument('posts', postId, {
|
|
483
|
+
views: FieldValue.increment(1),
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
### Leaderboard Query
|
|
489
|
+
|
|
490
|
+
```typescript
|
|
491
|
+
import { queryDocuments } from 'firebase-admin-sdk-v8';
|
|
492
|
+
|
|
493
|
+
async function getTopPlayers(limit: number = 10) {
|
|
494
|
+
return await queryDocuments('players', {
|
|
495
|
+
where: [
|
|
496
|
+
{ field: 'active', op: '==', value: true }
|
|
497
|
+
],
|
|
498
|
+
orderBy: [
|
|
499
|
+
{ field: 'score', direction: 'DESCENDING' }
|
|
500
|
+
],
|
|
501
|
+
limit
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
### Bulk User Update
|
|
507
|
+
|
|
508
|
+
```typescript
|
|
509
|
+
import { batchWrite, FieldValue } from 'firebase-admin-sdk-v8';
|
|
510
|
+
|
|
511
|
+
async function bulkUpdateUsers(userIds: string[], updates: any) {
|
|
512
|
+
const operations = userIds.map(userId => ({
|
|
513
|
+
type: 'update' as const,
|
|
514
|
+
collectionPath: 'users',
|
|
515
|
+
documentId: userId,
|
|
516
|
+
data: {
|
|
517
|
+
...updates,
|
|
518
|
+
updatedAt: FieldValue.serverTimestamp(),
|
|
519
|
+
}
|
|
520
|
+
}));
|
|
521
|
+
|
|
522
|
+
// Firestore batch limit is 500 operations
|
|
523
|
+
const chunks = [];
|
|
524
|
+
for (let i = 0; i < operations.length; i += 500) {
|
|
525
|
+
chunks.push(operations.slice(i, i + 500));
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
for (const chunk of chunks) {
|
|
529
|
+
await batchWrite(chunk);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
```
|