diff --git a/packages/dart_firebase_admin/example/lib/auth_example.dart b/packages/dart_firebase_admin/example/lib/auth_example.dart index b637b3bc..37696d77 100644 --- a/packages/dart_firebase_admin/example/lib/auth_example.dart +++ b/packages/dart_firebase_admin/example/lib/auth_example.dart @@ -191,3 +191,450 @@ Future tenantExample(FirebaseApp admin) async { } } } + +Future userManagementExample(FirebaseApp admin) async { + print('\n### User Management Example ###\n'); + + final auth = admin.auth(); + + // Create a temporary user to use throughout this example + late UserRecord tempUser; + try { + print('> Creating temporary user for example...\n'); + tempUser = await auth.createUser( + CreateRequest( + email: + 'temp-example-${DateTime.now().millisecondsSinceEpoch}@example.com', + password: 'TempPass@12345', + displayName: 'Temp Example User', + ), + ); + print('Temporary user created: ${tempUser.uid}\n'); + } on FirebaseAuthAdminException catch (e) { + print('> Failed to create temporary user: ${e.code} - ${e.message}'); + return; + } catch (e) { + print('> Failed to create temporary user: $e'); + return; + } + + // getUser + try { + print('> Fetching user by UID...\n'); + final user = await auth.getUser(tempUser.uid); + print('User: ${user.uid} — ${user.email}'); + } on FirebaseAuthAdminException catch (e) { + if (e.errorCode == AuthClientErrorCode.userNotFound) { + print('> User not found'); + } else { + print('> Auth error: ${e.code} - ${e.message}'); + } + } catch (e) { + print('> Error: $e'); + } + + // getUserByPhoneNumber + try { + print('> Fetching user by phone number...\n'); + final user = await auth.getUserByPhoneNumber('+15551234567'); + print('User by phone: ${user.uid}'); + } on FirebaseAuthAdminException catch (e) { + print('> Auth error: ${e.code} - ${e.message}'); + } catch (e) { + print('> Error: $e'); + } + + // getUserByProviderUid + try { + print('> Fetching user by provider UID...\n'); + final user = await auth.getUserByProviderUid( + providerId: 'google.com', + uid: 'google-uid-123', + ); + print('User by provider: ${user.uid}'); + } on FirebaseAuthAdminException catch (e) { + print('> Auth error: ${e.code} - ${e.message}'); + } catch (e) { + print('> Error: $e'); + } + + // getUsers (batch lookup) + try { + print('> Batch fetching users...\n'); + final result = await auth.getUsers([ + UidIdentifier(uid: tempUser.uid), + EmailIdentifier(email: tempUser.email!), + ]); + print('Found ${result.users.length} user(s)'); + print('Not found: ${result.notFound.length}'); + } on FirebaseAuthAdminException catch (e) { + print('> Auth error: ${e.code} - ${e.message}'); + } catch (e) { + print('> Error: $e'); + } + + // updateUser + try { + print('> Updating user...\n'); + final updated = await auth.updateUser( + tempUser.uid, + UpdateRequest(displayName: 'Updated Name', disabled: false), + ); + print('Updated user: ${updated.displayName}'); + } on FirebaseAuthAdminException catch (e) { + print('> Auth error: ${e.code} - ${e.message}'); + } catch (e) { + print('> Error: $e'); + } + + // listUsers + try { + print('> Listing users (first page)...\n'); + final result = await auth.listUsers(maxResults: 10); + print('Listed ${result.users.length} user(s)'); + if (result.pageToken != null) { + print('Next page token: ${result.pageToken}'); + } + } on FirebaseAuthAdminException catch (e) { + print('> Auth error: ${e.code} - ${e.message}'); + } catch (e) { + print('> Error: $e'); + } + + // importUsers + try { + print('> Importing users...\n'); + final importResult = await auth.importUsers([ + UserImportRecord(uid: 'import-uid-1', email: 'import1@example.com'), + UserImportRecord(uid: 'import-uid-2', email: 'import2@example.com'), + ]); + print( + 'Import complete: ${importResult.successCount} succeeded, ' + '${importResult.failureCount} failed', + ); + } on FirebaseAuthAdminException catch (e) { + print('> Auth error: ${e.code} - ${e.message}'); + } catch (e) { + print('> Error: $e'); + } + + // deleteUser — clean up the temporary user created at the start + try { + print('> Deleting temporary user...\n'); + await auth.deleteUser(tempUser.uid); + print('Temporary user deleted'); + } on FirebaseAuthAdminException catch (e) { + print('> Auth error: ${e.code} - ${e.message}'); + } catch (e) { + print('> Error: $e'); + } + + // deleteUsers + try { + print('> Deleting multiple users (demo with non-existent UIDs)...\n'); + final result = await auth.deleteUsers(['uid-a', 'uid-b', 'uid-c']); + print( + 'Deleted: ${result.successCount} succeeded, ' + '${result.failureCount} failed', + ); + } on FirebaseAuthAdminException catch (e) { + print('> Auth error: ${e.code} - ${e.message}'); + } catch (e) { + print('> Error: $e'); + } +} + +Future emailLinksExample(FirebaseApp admin) async { + print('\n### Email Action Links Example ###\n'); + + final auth = admin.auth(); + const email = 'user@example.com'; + final actionCodeSettings = ActionCodeSettings( + url: 'https://example.com/finishSignUp?cartId=1234', + handleCodeInApp: true, + iOS: ActionCodeSettingsIos('com.example.ios'), + android: ActionCodeSettingsAndroid( + packageName: 'com.example.android', + installApp: true, + minimumVersion: '12', + ), + ); + + // generatePasswordResetLink + try { + print('> Generating password reset link...\n'); + final link = await auth.generatePasswordResetLink( + email, + actionCodeSettings: actionCodeSettings, + ); + print('Password reset link: $link\n'); + } on FirebaseAuthAdminException catch (e) { + print('> Auth error: ${e.code} - ${e.message}'); + } catch (e) { + print('> Error: $e'); + } + + // generateEmailVerificationLink + try { + print('> Generating email verification link...\n'); + final link = await auth.generateEmailVerificationLink( + email, + actionCodeSettings: actionCodeSettings, + ); + print('Email verification link: $link\n'); + } on FirebaseAuthAdminException catch (e) { + print('> Auth error: ${e.code} - ${e.message}'); + } catch (e) { + print('> Error: $e'); + } + + // generateVerifyAndChangeEmailLink + try { + print('> Generating verify-and-change-email link...\n'); + final link = await auth.generateVerifyAndChangeEmailLink( + email, + 'newemail@example.com', + actionCodeSettings: actionCodeSettings, + ); + print('Verify-and-change-email link: $link\n'); + } on FirebaseAuthAdminException catch (e) { + print('> Auth error: ${e.code} - ${e.message}'); + } catch (e) { + print('> Error: $e'); + } + + // generateSignInWithEmailLink + try { + print('> Generating sign-in-with-email link...\n'); + final link = await auth.generateSignInWithEmailLink( + email, + actionCodeSettings, + ); + print('Sign-in-with-email link: $link\n'); + } on FirebaseAuthAdminException catch (e) { + print('> Auth error: ${e.code} - ${e.message}'); + } catch (e) { + print('> Error: $e'); + } +} + +Future tokenExample(FirebaseApp admin) async { + print('\n### Custom Tokens & ID Token Verification Example ###\n'); + + final auth = admin.auth(); + const uid = 'some-uid'; + + // setCustomUserClaims + try { + print('> Setting custom user claims...\n'); + await auth.setCustomUserClaims( + uid, + customUserClaims: {'admin': true, 'accessLevel': 5}, + ); + print('Custom claims set\n'); + } on FirebaseAuthAdminException catch (e) { + print('> Auth error: ${e.code} - ${e.message}'); + } catch (e) { + print('> Error: $e'); + } + + // createCustomToken + try { + print('> Creating custom token...\n'); + final customToken = await auth.createCustomToken(uid); + print( + 'Custom token (first 40 chars): ${customToken.substring(0, 40)}...\n', + ); + } on FirebaseAuthAdminException catch (e) { + print('> Auth error: ${e.code} - ${e.message}'); + } catch (e) { + print('> Error: $e'); + } + + // verifyIdToken + // To obtain a real ID token, use the Firebase client SDK on the client side: + // - Flutter: await FirebaseAuth.instance.currentUser?.getIdToken() + // - Web: await firebase.auth().currentUser?.getIdToken() + // - Android: FirebaseAuth.getInstance().currentUser?.getIdToken(false) + // - iOS/macOS: Auth.auth().currentUser?.getIDToken(completion:) + // The client sends the token to your server, which then verifies it here. + try { + print('> Verifying ID token...\n'); + final decoded = await auth.verifyIdToken(''); + print('Decoded token:'); + print(' - uid: ${decoded.uid}'); + print(' - email: ${decoded.email}'); + print(' - iss: ${decoded.iss}'); + print(''); + } on FirebaseAuthAdminException catch (e) { + print('> Auth error: ${e.code} - ${e.message}'); + } catch (e) { + print('> Error: $e'); + } + + // revokeRefreshTokens + verifyIdToken with checkRevoked + try { + print('> Revoking refresh tokens for user...\n'); + await auth.revokeRefreshTokens(uid); + print('Refresh tokens revoked\n'); + + print('> Verifying ID token with revocation check...\n'); + final decoded = await auth.verifyIdToken( + '', + checkRevoked: true, + ); + print('Token is still valid: uid=${decoded.uid}\n'); + } on FirebaseAuthAdminException catch (e) { + if (e.errorCode == AuthClientErrorCode.idTokenRevoked) { + print('> Token has been revoked — require re-authentication'); + } else { + print('> Auth error: ${e.code} - ${e.message}'); + } + } catch (e) { + print('> Error: $e'); + } +} + +Future sessionCookieExample(FirebaseApp admin) async { + print('\n### Session Cookie Example ###\n'); + + final auth = admin.auth(); + + // createSessionCookie + // An ID token must first be obtained from the client (see verifyIdToken + // comments above for platform-specific examples). Typically the client + // sends the token to your server via an HTTP request, and the server + // exchanges it for a long-lived session cookie to store in a secure, + // HttpOnly cookie. + try { + print('> Creating session cookie...\n'); + final sessionCookie = await auth.createSessionCookie( + '', + SessionCookieOptions(expiresIn: const Duration(days: 5).inMilliseconds), + ); + print( + 'Session cookie created (first 40 chars): ' + '${sessionCookie.substring(0, 40)}...\n', + ); + + // verifySessionCookie + print('> Verifying session cookie...\n'); + final decoded = await auth.verifySessionCookie(sessionCookie); + print('Session cookie valid: uid=${decoded.uid}\n'); + } on FirebaseAuthAdminException catch (e) { + if (e.errorCode == AuthClientErrorCode.sessionCookieRevoked) { + print('> Session cookie has been revoked'); + } else { + print('> Auth error: ${e.code} - ${e.message}'); + } + } catch (e) { + print('> Error: $e'); + } +} + +Future providerConfigExample(FirebaseApp admin) async { + print('\n### Provider Config Example ###\n'); + + final auth = admin.auth(); + const oidcProviderId = 'oidc.my-provider'; + const samlProviderId = 'saml.my-provider'; + + // createProviderConfig (OIDC) + try { + print('> Creating OIDC provider config...\n'); + final config = await auth.createProviderConfig( + OIDCAuthProviderConfig( + providerId: oidcProviderId, + clientId: 'my-client-id', + issuer: 'https://accounts.google.com', + displayName: 'My OIDC Provider', + enabled: true, + ), + ); + print('OIDC provider created: ${config.providerId}\n'); + } on FirebaseAuthAdminException catch (e) { + print('> Auth error: ${e.code} - ${e.message}'); + } catch (e) { + print('> Error: $e'); + } + + // createProviderConfig (SAML) + try { + print('> Creating SAML provider config...\n'); + final config = await auth.createProviderConfig( + SAMLAuthProviderConfig( + providerId: samlProviderId, + idpEntityId: 'https://idp.example.com', + ssoURL: 'https://idp.example.com/sso', + x509Certificates: [ + '-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----', + ], + rpEntityId: 'my-rp-entity-id', + displayName: 'My SAML Provider', + enabled: true, + ), + ); + print('SAML provider created: ${config.providerId}\n'); + } on FirebaseAuthAdminException catch (e) { + print('> Auth error: ${e.code} - ${e.message}'); + } catch (e) { + print('> Error: $e'); + } + + // getProviderConfig + try { + print('> Fetching OIDC provider config...\n'); + final config = await auth.getProviderConfig(oidcProviderId); + print('Provider: ${config.providerId} — enabled: ${config.enabled}\n'); + } on FirebaseAuthAdminException catch (e) { + print('> Auth error: ${e.code} - ${e.message}'); + } catch (e) { + print('> Error: $e'); + } + + // updateProviderConfig + try { + print('> Updating OIDC provider config...\n'); + final updated = await auth.updateProviderConfig( + oidcProviderId, + OIDCUpdateAuthProviderRequest(displayName: 'Updated OIDC Provider'), + ); + print('Updated provider: ${updated.displayName}\n'); + } on FirebaseAuthAdminException catch (e) { + print('> Auth error: ${e.code} - ${e.message}'); + } catch (e) { + print('> Error: $e'); + } + + // listProviderConfigs + try { + print('> Listing OIDC provider configs...\n'); + final result = await auth.listProviderConfigs( + AuthProviderConfigFilter.oidc(maxResults: 10), + ); + print('Found ${result.providerConfigs.length} OIDC provider(s)'); + if (result.pageToken != null) { + print('Next page token: ${result.pageToken}'); + } + print(''); + } on FirebaseAuthAdminException catch (e) { + print('> Auth error: ${e.code} - ${e.message}'); + } catch (e) { + print('> Error: $e'); + } + + // deleteProviderConfig + try { + print('> Deleting OIDC provider config...\n'); + await auth.deleteProviderConfig(oidcProviderId); + print('OIDC provider deleted\n'); + + print('> Deleting SAML provider config...\n'); + await auth.deleteProviderConfig(samlProviderId); + print('SAML provider deleted\n'); + } on FirebaseAuthAdminException catch (e) { + print('> Auth error: ${e.code} - ${e.message}'); + } catch (e) { + print('> Error: $e'); + } +} diff --git a/packages/dart_firebase_admin/example/lib/firestore_example.dart b/packages/dart_firebase_admin/example/lib/firestore_example.dart index 3354b3aa..8b640830 100644 --- a/packages/dart_firebase_admin/example/lib/firestore_example.dart +++ b/packages/dart_firebase_admin/example/lib/firestore_example.dart @@ -8,6 +8,12 @@ Future firestoreExample(FirebaseApp admin) async { await basicFirestoreExample(admin); await multiDatabaseExample(admin); + await batchExample(admin); + await transactionExample(admin); + await collectionGroupExample(admin); + await getAllExample(admin); + await listCollectionsExample(admin); + await recursiveDeleteExample(admin); await bulkWriterExamples(admin); await bundleBuilderExample(admin); } @@ -260,3 +266,204 @@ Future bundleBuilderExample(FirebaseApp admin) async { print('> Error creating bundle: $e'); } } + +/// WriteBatch example +Future batchExample(FirebaseApp admin) async { + print('### WriteBatch Example ###\n'); + + final firestore = admin.firestore(); + + // Simulate an order placement: atomically create the order, record it on + // the user's profile, and decrement the product's stock — all in one batch + // so either every write succeeds or none of them do. + try { + print('> Setting up initial product and user documents...\n'); + + final productRef = firestore.collection('products').doc('widget-42'); + final userRef = firestore.collection('users').doc('user-1'); + + await productRef.set({'name': 'Widget', 'stock': 10}); + await userRef.set({'name': 'Alice', 'orderCount': 2}); + + print('> Placing order atomically with batch...\n'); + + final batch = firestore.batch(); + final orderRef = firestore.collection('orders').doc(); + + // 1. Create the new order document. + batch.set(orderRef, { + 'userId': 'user-1', + 'productId': 'widget-42', + 'quantity': 1, + 'status': 'confirmed', + }); + + // 2. Increment the user's order count. + batch.update(userRef, { + FieldPath(const ['orderCount']): 3, + }); + + // 3. Decrement the product stock. + batch.update(productRef, { + FieldPath(const ['stock']): 9, + }); + + await batch.commit(); + print('> Batch committed successfully\n'); + + // Clean up + await Future.wait([ + orderRef.delete(), + productRef.delete(), + userRef.delete(), + ]); + } catch (e) { + print('> Error: $e'); + } +} + +/// runTransaction example +Future transactionExample(FirebaseApp admin) async { + print('### runTransaction Example ###\n'); + + final firestore = admin.firestore(); + + try { + print('> Running a transaction...\n'); + + final docRef = firestore.collection('counters').doc('visits'); + await docRef.set({'count': 0}); + + await firestore.runTransaction((transaction) async { + final snapshot = await transaction.get(docRef); + final current = (snapshot.data()?['count'] as int?) ?? 0; + transaction.update(docRef, { + FieldPath(const ['count']): current + 1, + }); + }); + + final updated = await docRef.get(); + print('> Counter after transaction: ${updated.data()?['count']}\n'); + + // Clean up + await docRef.delete(); + } catch (e) { + print('> Error: $e'); + } +} + +/// collectionGroup example +Future collectionGroupExample(FirebaseApp admin) async { + print('### collectionGroup Example ###\n'); + + final firestore = admin.firestore(); + + final review1 = firestore + .collection('restaurants') + .doc('pizza-place') + .collection('reviews') + .doc('review-1'); + + final review2 = firestore + .collection('restaurants') + .doc('burger-joint') + .collection('reviews') + .doc('review-2'); + + try { + print('> Querying all "reviews" subcollections across documents...\n'); + + // Set up sample data + await review1.set({'rating': 5, 'text': 'Great pizza!'}); + await review2.set({'rating': 4, 'text': 'Good burgers'}); + + final query = firestore.collectionGroup('reviews'); + final snapshot = await query.get(); + + print('Found ${snapshot.docs.length} review(s) across all restaurants:'); + for (final doc in snapshot.docs) { + print(' - ${doc.ref.path}: rating=${doc.data()['rating']}'); + } + print(''); + } catch (e) { + print('> Error: $e'); + } finally { + // Clean up sample data regardless of success or failure + await Future.wait([review1.delete(), review2.delete()]); + } +} + +/// getAll example +Future getAllExample(FirebaseApp admin) async { + print('### getAll Example ###\n'); + + final firestore = admin.firestore(); + + try { + print('> Fetching multiple documents in one request...\n'); + + final col = firestore.collection('getall-demo'); + await col.doc('a').set({'value': 1}); + await col.doc('b').set({'value': 2}); + + final refs = [col.doc('a'), col.doc('b'), col.doc('missing')]; + final snapshots = await firestore.getAll(refs); + + for (final snap in snapshots) { + if (snap.exists) { + print(' ${snap.ref.id}: ${snap.data()}'); + } else { + print(' ${snap.ref.id}: does not exist'); + } + } + print(''); + + // Clean up + await col.doc('a').delete(); + await col.doc('b').delete(); + } catch (e) { + print('> Error: $e'); + } +} + +/// listCollections example +Future listCollectionsExample(FirebaseApp admin) async { + print('### listCollections Example ###\n'); + + final firestore = admin.firestore(); + + try { + print('> Listing root-level collections...\n'); + + final collections = await firestore.listCollections(); + print('Found ${collections.length} collection(s):'); + for (final col in collections) { + print(' - ${col.id}'); + } + print(''); + } catch (e) { + print('> Error: $e'); + } +} + +/// recursiveDelete example +Future recursiveDeleteExample(FirebaseApp admin) async { + print('### recursiveDelete Example ###\n'); + + final firestore = admin.firestore(); + + try { + print('> Setting up nested data to delete...\n'); + + final parent = firestore.collection('recursive-demo').doc('parent'); + await parent.set({'name': 'parent'}); + await parent.collection('children').doc('child-1').set({'name': 'child 1'}); + await parent.collection('children').doc('child-2').set({'name': 'child 2'}); + + print('> Recursively deleting document and all subcollections...\n'); + await firestore.recursiveDelete(parent); + print('Recursive delete complete\n'); + } catch (e) { + print('> Error: $e'); + } +} diff --git a/packages/dart_firebase_admin/example/lib/functions_example.dart b/packages/dart_firebase_admin/example/lib/functions_example.dart index d118b93d..c0706e0e 100644 --- a/packages/dart_firebase_admin/example/lib/functions_example.dart +++ b/packages/dart_firebase_admin/example/lib/functions_example.dart @@ -11,6 +11,9 @@ Future functionsExample(FirebaseApp admin) async { final functions = admin.functions(); + // Accessing the app property + print('> Functions app name: ${functions.app.name}\n'); + // Get a task queue reference // The function name should match an existing Cloud Function or queue name final taskQueue = functions.taskQueue('helloWorld'); @@ -70,7 +73,23 @@ Future functionsExample(FirebaseApp admin) async { } } - // Example 5: Delete a task + // Example 5: Enqueue with experimental URI override + try { + print('> Enqueuing a task with a custom handler URI...\n'); + await taskQueue.enqueue( + {'action': 'customHandler'}, + TaskOptions( + experimental: TaskOptionsExperimental( + uri: 'https://custom.example.com/task-handler', + ), + ), + ); + print('Task with experimental URI enqueued!\n'); + } on FirebaseFunctionsAdminException catch (e) { + print('> Functions error: ${e.code} - ${e.message}\n'); + } + + // Example 6: Delete a task try { print('> Deleting task...\n'); await taskQueue.delete('payment-order-456'); diff --git a/packages/dart_firebase_admin/example/lib/messaging_example.dart b/packages/dart_firebase_admin/example/lib/messaging_example.dart index ce8b73fe..3b1356d1 100644 --- a/packages/dart_firebase_admin/example/lib/messaging_example.dart +++ b/packages/dart_firebase_admin/example/lib/messaging_example.dart @@ -85,7 +85,29 @@ Future messagingExample(FirebaseApp admin) async { print('> Error sending multicast: $e'); } - // Example 4: Subscribe tokens to a topic + // Example 4: Send a message with a condition + try { + print('> Sending ConditionMessage...\n'); + final messageId = await messaging.send( + ConditionMessage( + condition: "'TopicA' in topics || 'TopicB' in topics", + notification: Notification( + title: 'Condition Message', + body: 'Sent to subscribers of TopicA or TopicB', + ), + ), + dryRun: true, + ); + print('ConditionMessage sent!'); + print(' - Message ID: $messageId'); + print(''); + } on FirebaseMessagingAdminException catch (e) { + print('> Messaging error: ${e.code} - ${e.message}'); + } catch (e) { + print('> Error sending condition message: $e'); + } + + // Example 5: Subscribe tokens to a topic try { print('> Subscribing tokens to topic: test-topic\n'); // Note: Using fake token for demonstration @@ -112,7 +134,7 @@ Future messagingExample(FirebaseApp admin) async { print('> Error subscribing to topic: $e'); } - // Example 5: Send with platform-specific options + // Example 6: Send with platform-specific options try { print('> Sending message with platform-specific options...\n'); final messageId = await messaging.send(