@umituz/react-native-firebase 1.13.36 → 1.13.37

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-firebase",
3
- "version": "1.13.36",
3
+ "version": "1.13.37",
4
4
  "description": "Unified Firebase package for React Native apps - Auth and Firestore services using Firebase JS SDK (no native modules).",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -49,16 +49,25 @@
49
49
  "expo-apple-authentication": "^8.0.8",
50
50
  "expo-crypto": "^15.0.8",
51
51
  "firebase": "^12.6.0",
52
+ "firebase-admin": "^13.0.2",
52
53
  "react": "19.1.0",
53
54
  "react-native": "0.81.5",
54
55
  "typescript": "~5.9.2"
55
56
  },
57
+ "optionalDependencies": {
58
+ "firebase-admin": "^13.0.2"
59
+ },
56
60
  "publishConfig": {
57
61
  "access": "public"
58
62
  },
59
63
  "files": [
60
64
  "src",
65
+ "scripts",
61
66
  "README.md",
62
67
  "LICENSE"
63
- ]
68
+ ],
69
+ "exports": {
70
+ ".": "./src/index.ts",
71
+ "./scripts": "./scripts/index.ts"
72
+ }
64
73
  }
@@ -0,0 +1,142 @@
1
+ /**
2
+ * Firebase Admin Auth Utilities
3
+ * Generic auth operations for admin scripts
4
+ */
5
+
6
+ import * as admin from "firebase-admin";
7
+ import type { UserInfo, CleanupResult } from "./types";
8
+
9
+ /**
10
+ * List all users from Firebase Auth
11
+ */
12
+ export async function listAllUsers(auth: admin.auth.Auth): Promise<UserInfo[]> {
13
+ const users: UserInfo[] = [];
14
+ let nextPageToken: string | undefined;
15
+
16
+ do {
17
+ const result = await auth.listUsers(1000, nextPageToken);
18
+
19
+ result.users.forEach((user) => {
20
+ users.push({
21
+ uid: user.uid,
22
+ email: user.email,
23
+ displayName: user.displayName,
24
+ isAnonymous: !user.providerData || user.providerData.length === 0,
25
+ createdAt: user.metadata.creationTime
26
+ ? new Date(user.metadata.creationTime)
27
+ : undefined,
28
+ providerCount: user.providerData?.length ?? 0,
29
+ });
30
+ });
31
+
32
+ nextPageToken = result.pageToken;
33
+ } while (nextPageToken);
34
+
35
+ return users;
36
+ }
37
+
38
+ /**
39
+ * List only authenticated users (with email/providers)
40
+ */
41
+ export async function listAuthenticatedUsers(
42
+ auth: admin.auth.Auth
43
+ ): Promise<UserInfo[]> {
44
+ const allUsers = await listAllUsers(auth);
45
+ return allUsers.filter((user) => !user.isAnonymous && user.email);
46
+ }
47
+
48
+ /**
49
+ * List only anonymous users
50
+ */
51
+ export async function listAnonymousUsers(
52
+ auth: admin.auth.Auth
53
+ ): Promise<UserInfo[]> {
54
+ const allUsers = await listAllUsers(auth);
55
+ return allUsers.filter((user) => user.isAnonymous);
56
+ }
57
+
58
+ /**
59
+ * Delete users by UIDs
60
+ */
61
+ export async function deleteUsers(
62
+ auth: admin.auth.Auth,
63
+ uids: string[],
64
+ onProgress?: (deleted: number, total: number) => void
65
+ ): Promise<CleanupResult> {
66
+ const result: CleanupResult = {
67
+ totalProcessed: uids.length,
68
+ deleted: 0,
69
+ preserved: 0,
70
+ errors: [],
71
+ };
72
+
73
+ for (const uid of uids) {
74
+ try {
75
+ await auth.deleteUser(uid);
76
+ result.deleted++;
77
+ onProgress?.(result.deleted, uids.length);
78
+ } catch (error) {
79
+ result.errors.push(`Failed to delete ${uid}: ${error}`);
80
+ }
81
+ }
82
+
83
+ return result;
84
+ }
85
+
86
+ /**
87
+ * Cleanup anonymous users - delete all users without providers
88
+ */
89
+ export async function cleanupAnonymousUsers(
90
+ auth: admin.auth.Auth,
91
+ onProgress?: (deleted: number, total: number) => void
92
+ ): Promise<CleanupResult> {
93
+ const anonymousUsers = await listAnonymousUsers(auth);
94
+ const uids = anonymousUsers.map((u) => u.uid);
95
+
96
+ if (uids.length === 0) {
97
+ return {
98
+ totalProcessed: 0,
99
+ deleted: 0,
100
+ preserved: 0,
101
+ errors: [],
102
+ };
103
+ }
104
+
105
+ const deleteResult = await deleteUsers(auth, uids, onProgress);
106
+
107
+ // Count preserved (authenticated) users
108
+ const authenticatedUsers = await listAuthenticatedUsers(auth);
109
+ deleteResult.preserved = authenticatedUsers.length;
110
+
111
+ return deleteResult;
112
+ }
113
+
114
+ /**
115
+ * Delete all users from Firebase Auth
116
+ */
117
+ export async function deleteAllUsers(
118
+ auth: admin.auth.Auth,
119
+ onProgress?: (deleted: number, total: number) => void
120
+ ): Promise<CleanupResult> {
121
+ const allUsers = await listAllUsers(auth);
122
+ const uids = allUsers.map((u) => u.uid);
123
+ return deleteUsers(auth, uids, onProgress);
124
+ }
125
+
126
+ /**
127
+ * Get user statistics
128
+ */
129
+ export async function getUserStats(auth: admin.auth.Auth): Promise<{
130
+ total: number;
131
+ anonymous: number;
132
+ authenticated: number;
133
+ }> {
134
+ const allUsers = await listAllUsers(auth);
135
+ const anonymous = allUsers.filter((u) => u.isAnonymous).length;
136
+
137
+ return {
138
+ total: allUsers.length,
139
+ anonymous,
140
+ authenticated: allUsers.length - anonymous,
141
+ };
142
+ }
@@ -0,0 +1,280 @@
1
+ /**
2
+ * Firebase Admin Firestore Utilities
3
+ * Generic Firestore operations for admin scripts
4
+ */
5
+
6
+ import * as admin from "firebase-admin";
7
+ import type { CollectionInfo, BatchResult } from "./types";
8
+
9
+ const BATCH_SIZE = 500;
10
+
11
+ /**
12
+ * List all root-level collections
13
+ */
14
+ export async function listCollections(
15
+ db: admin.firestore.Firestore
16
+ ): Promise<CollectionInfo[]> {
17
+ const collections = await db.listCollections();
18
+ const result: CollectionInfo[] = [];
19
+
20
+ for (const collection of collections) {
21
+ const snapshot = await collection.limit(1000).get();
22
+ const info: CollectionInfo = {
23
+ name: collection.id,
24
+ documentCount: snapshot.docs.length,
25
+ sampleDocumentId: snapshot.docs[0]?.id,
26
+ };
27
+
28
+ if (!snapshot.empty) {
29
+ const subcollections = await snapshot.docs[0].ref.listCollections();
30
+ info.hasSubcollections = subcollections.length > 0;
31
+ }
32
+
33
+ result.push(info);
34
+ }
35
+
36
+ return result.sort((a, b) => b.documentCount - a.documentCount);
37
+ }
38
+
39
+ /**
40
+ * List subcollections for a user document
41
+ */
42
+ export async function listUserSubcollections(
43
+ db: admin.firestore.Firestore,
44
+ userId: string
45
+ ): Promise<CollectionInfo[]> {
46
+ const userRef = db.collection("users").doc(userId);
47
+ const subcollections = await userRef.listCollections();
48
+ const result: CollectionInfo[] = [];
49
+
50
+ for (const subcollection of subcollections) {
51
+ const count = await subcollection.count().get();
52
+ result.push({
53
+ name: subcollection.id,
54
+ documentCount: count.data().count,
55
+ });
56
+ }
57
+
58
+ return result;
59
+ }
60
+
61
+ /**
62
+ * Delete collection in batches
63
+ */
64
+ export async function deleteCollection(
65
+ db: admin.firestore.Firestore,
66
+ collectionPath: string,
67
+ onProgress?: (deleted: number) => void
68
+ ): Promise<number> {
69
+ let totalDeleted = 0;
70
+ let hasMore = true;
71
+
72
+ while (hasMore) {
73
+ const snapshot = await db
74
+ .collection(collectionPath)
75
+ .orderBy("__name__")
76
+ .limit(BATCH_SIZE)
77
+ .get();
78
+
79
+ if (snapshot.empty) {
80
+ hasMore = false;
81
+ continue;
82
+ }
83
+
84
+ const batch = db.batch();
85
+ snapshot.docs.forEach((doc) => batch.delete(doc.ref));
86
+ await batch.commit();
87
+
88
+ totalDeleted += snapshot.docs.length;
89
+ onProgress?.(totalDeleted);
90
+ }
91
+
92
+ return totalDeleted;
93
+ }
94
+
95
+ /**
96
+ * Delete user subcollection for all users
97
+ */
98
+ export async function deleteUserSubcollection(
99
+ db: admin.firestore.Firestore,
100
+ subcollectionName: string,
101
+ onProgress?: (deleted: number) => void
102
+ ): Promise<number> {
103
+ let totalDeleted = 0;
104
+ const usersSnapshot = await db.collection("users").get();
105
+
106
+ for (const userDoc of usersSnapshot.docs) {
107
+ const subcollectionRef = userDoc.ref.collection(subcollectionName);
108
+ const subcollectionSnapshot = await subcollectionRef.get();
109
+
110
+ if (!subcollectionSnapshot.empty) {
111
+ const batch = db.batch();
112
+ subcollectionSnapshot.docs.forEach((doc) => batch.delete(doc.ref));
113
+ await batch.commit();
114
+ totalDeleted += subcollectionSnapshot.docs.length;
115
+ onProgress?.(totalDeleted);
116
+ }
117
+ }
118
+
119
+ return totalDeleted;
120
+ }
121
+
122
+ /**
123
+ * Delete all Firestore data
124
+ */
125
+ export async function deleteAllData(
126
+ db: admin.firestore.Firestore,
127
+ onProgress?: (collection: string, deleted: number) => void
128
+ ): Promise<number> {
129
+ let totalDeleted = 0;
130
+ const collections = await db.listCollections();
131
+
132
+ for (const collection of collections) {
133
+ const snapshot = await collection.get();
134
+
135
+ // Delete subcollections first for users collection
136
+ if (collection.id === "users") {
137
+ for (const doc of snapshot.docs) {
138
+ const subcollections = await doc.ref.listCollections();
139
+ for (const subcollection of subcollections) {
140
+ const subSnapshot = await subcollection.get();
141
+ if (!subSnapshot.empty) {
142
+ const batch = db.batch();
143
+ subSnapshot.docs.forEach((subDoc) => batch.delete(subDoc.ref));
144
+ await batch.commit();
145
+ totalDeleted += subSnapshot.docs.length;
146
+ }
147
+ }
148
+ }
149
+ }
150
+
151
+ // Delete main collection documents
152
+ if (!snapshot.empty) {
153
+ const batch = db.batch();
154
+ snapshot.docs.forEach((doc) => batch.delete(doc.ref));
155
+ await batch.commit();
156
+ totalDeleted += snapshot.docs.length;
157
+ onProgress?.(collection.id, totalDeleted);
158
+ }
159
+ }
160
+
161
+ return totalDeleted;
162
+ }
163
+
164
+ /**
165
+ * Seed documents in batches
166
+ */
167
+ export async function seedBatch(
168
+ db: admin.firestore.Firestore,
169
+ collectionPath: string,
170
+ docs: Array<{ id: string; data: Record<string, unknown> }>
171
+ ): Promise<BatchResult> {
172
+ const result: BatchResult = {
173
+ success: true,
174
+ processed: 0,
175
+ errors: [],
176
+ };
177
+
178
+ for (let i = 0; i < docs.length; i += BATCH_SIZE) {
179
+ const batch = db.batch();
180
+ const slice = docs.slice(i, i + BATCH_SIZE);
181
+
182
+ for (const { id, data } of slice) {
183
+ const ref = db.collection(collectionPath).doc(id);
184
+ const clean = Object.fromEntries(
185
+ Object.entries(data).filter(([, v]) => v !== undefined)
186
+ );
187
+ batch.set(ref, clean);
188
+ }
189
+
190
+ try {
191
+ await batch.commit();
192
+ result.processed += slice.length;
193
+ } catch (error) {
194
+ result.success = false;
195
+ result.errors.push(`Batch failed at index ${i}: ${error}`);
196
+ }
197
+ }
198
+
199
+ return result;
200
+ }
201
+
202
+ /**
203
+ * Seed user subcollection
204
+ */
205
+ export async function seedUserSubcollection(
206
+ db: admin.firestore.Firestore,
207
+ userId: string,
208
+ subcollectionName: string,
209
+ docs: Array<{ id: string; data: Record<string, unknown> }>
210
+ ): Promise<BatchResult> {
211
+ const result: BatchResult = {
212
+ success: true,
213
+ processed: 0,
214
+ errors: [],
215
+ };
216
+
217
+ const batch = db.batch();
218
+
219
+ for (const { id, data } of docs) {
220
+ const ref = db
221
+ .collection("users")
222
+ .doc(userId)
223
+ .collection(subcollectionName)
224
+ .doc(id);
225
+ const clean = Object.fromEntries(
226
+ Object.entries(data).filter(([, v]) => v !== undefined)
227
+ );
228
+ batch.set(ref, clean);
229
+ }
230
+
231
+ try {
232
+ await batch.commit();
233
+ result.processed = docs.length;
234
+ } catch (error) {
235
+ result.success = false;
236
+ result.errors.push(`Failed to seed subcollection: ${error}`);
237
+ }
238
+
239
+ return result;
240
+ }
241
+
242
+ /**
243
+ * Count documents in collection
244
+ */
245
+ export async function countDocuments(
246
+ db: admin.firestore.Firestore,
247
+ collectionPath: string
248
+ ): Promise<number> {
249
+ const count = await db.collection(collectionPath).count().get();
250
+ return count.data().count;
251
+ }
252
+
253
+ /**
254
+ * Get user document count statistics
255
+ */
256
+ export async function getUserStats(db: admin.firestore.Firestore): Promise<{
257
+ total: number;
258
+ anonymous: number;
259
+ authenticated: number;
260
+ }> {
261
+ const usersSnapshot = await db.collection("users").get();
262
+
263
+ let anonymous = 0;
264
+ let authenticated = 0;
265
+
266
+ usersSnapshot.docs.forEach((doc) => {
267
+ const data = doc.data();
268
+ if (data.isAnonymous) {
269
+ anonymous++;
270
+ } else {
271
+ authenticated++;
272
+ }
273
+ });
274
+
275
+ return {
276
+ total: usersSnapshot.docs.length,
277
+ anonymous,
278
+ authenticated,
279
+ };
280
+ }
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Firebase Admin Scripts
3
+ *
4
+ * Generic utilities for Firebase Admin operations.
5
+ * Use these for CLI scripts, seeding, cleanup, and testing.
6
+ *
7
+ * Usage:
8
+ * import { initFirebaseAdmin, cleanupAnonymousUsers } from "@umituz/react-native-firebase/scripts";
9
+ *
10
+ * const app = initFirebaseAdmin({
11
+ * serviceAccountPath: "./service-account.json",
12
+ * projectId: "my-project",
13
+ * storageBucket: "my-project.appspot.com",
14
+ * });
15
+ *
16
+ * const auth = getAuthAdmin(app);
17
+ * await cleanupAnonymousUsers(auth);
18
+ */
19
+
20
+ // Types
21
+ export type {
22
+ FirebaseAdminConfig,
23
+ CollectionInfo,
24
+ UserInfo,
25
+ CleanupResult,
26
+ BatchResult,
27
+ StorageFileInfo,
28
+ ResetSummary,
29
+ } from "./types";
30
+
31
+ // Initialization
32
+ export {
33
+ initFirebaseAdmin,
34
+ getFirestoreAdmin,
35
+ getAuthAdmin,
36
+ getStorageAdmin,
37
+ resetFirebaseAdmin,
38
+ } from "./init";
39
+
40
+ // Auth utilities
41
+ export {
42
+ listAllUsers,
43
+ listAuthenticatedUsers,
44
+ listAnonymousUsers,
45
+ deleteUsers,
46
+ cleanupAnonymousUsers,
47
+ deleteAllUsers,
48
+ getUserStats as getAuthUserStats,
49
+ } from "./auth";
50
+
51
+ // Firestore utilities
52
+ export {
53
+ listCollections,
54
+ listUserSubcollections,
55
+ deleteCollection,
56
+ deleteUserSubcollection,
57
+ deleteAllData,
58
+ seedBatch,
59
+ seedUserSubcollection,
60
+ countDocuments,
61
+ getUserStats as getFirestoreUserStats,
62
+ } from "./firestore";
63
+
64
+ // Storage utilities
65
+ export {
66
+ listFiles,
67
+ deleteAllFiles,
68
+ deleteFilesByPrefix,
69
+ getStorageStats,
70
+ deleteUserFiles,
71
+ } from "./storage";
72
+
73
+ // Utility functions
74
+ export {
75
+ randomId,
76
+ randomDate,
77
+ randomItem,
78
+ randomNumber,
79
+ randomBoolean,
80
+ sleep,
81
+ formatBytes,
82
+ createConfirmationTimer,
83
+ printSeparator,
84
+ printHeader,
85
+ } from "./utils";
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Firebase Admin Initialization
3
+ * Dynamic configuration - no hardcoded values
4
+ */
5
+
6
+ import * as admin from "firebase-admin";
7
+ import type { FirebaseAdminConfig } from "./types";
8
+
9
+ let initializedApp: admin.app.App | null = null;
10
+
11
+ /**
12
+ * Initialize Firebase Admin SDK with config
13
+ * @param config - Firebase Admin configuration
14
+ * @returns Initialized Firebase Admin app
15
+ */
16
+ export function initFirebaseAdmin(config: FirebaseAdminConfig): admin.app.App {
17
+ if (initializedApp) {
18
+ return initializedApp;
19
+ }
20
+
21
+ if (admin.apps.length > 0) {
22
+ initializedApp = admin.apps[0]!;
23
+ return initializedApp;
24
+ }
25
+
26
+ initializedApp = admin.initializeApp({
27
+ credential: admin.credential.cert(config.serviceAccountPath),
28
+ projectId: config.projectId,
29
+ storageBucket: config.storageBucket,
30
+ });
31
+
32
+ return initializedApp;
33
+ }
34
+
35
+ /**
36
+ * Get Firestore instance
37
+ */
38
+ export function getFirestoreAdmin(app: admin.app.App): admin.firestore.Firestore {
39
+ return admin.firestore(app);
40
+ }
41
+
42
+ /**
43
+ * Get Auth instance
44
+ */
45
+ export function getAuthAdmin(app: admin.app.App): admin.auth.Auth {
46
+ return admin.auth(app);
47
+ }
48
+
49
+ /**
50
+ * Get Storage bucket
51
+ */
52
+ export function getStorageAdmin(app: admin.app.App): admin.storage.Storage {
53
+ return admin.storage(app);
54
+ }
55
+
56
+ /**
57
+ * Reset initialized app (for testing)
58
+ */
59
+ export function resetFirebaseAdmin(): void {
60
+ initializedApp = null;
61
+ }
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Firebase Admin Storage Utilities
3
+ * Generic Storage operations for admin scripts
4
+ */
5
+
6
+ import * as admin from "firebase-admin";
7
+ import type { StorageFileInfo, CleanupResult } from "./types";
8
+
9
+ /**
10
+ * List all files in storage bucket
11
+ */
12
+ export async function listFiles(
13
+ storage: admin.storage.Storage,
14
+ prefix?: string
15
+ ): Promise<StorageFileInfo[]> {
16
+ const bucket = storage.bucket();
17
+ const [files] = await bucket.getFiles({ prefix });
18
+
19
+ return files.map((file) => ({
20
+ name: file.name,
21
+ size: parseInt(file.metadata.size as string, 10) || 0,
22
+ contentType: file.metadata.contentType,
23
+ createdAt: file.metadata.timeCreated
24
+ ? new Date(file.metadata.timeCreated)
25
+ : undefined,
26
+ }));
27
+ }
28
+
29
+ /**
30
+ * Delete all files in storage
31
+ */
32
+ export async function deleteAllFiles(
33
+ storage: admin.storage.Storage,
34
+ onProgress?: (deleted: number, total: number) => void
35
+ ): Promise<CleanupResult> {
36
+ const bucket = storage.bucket();
37
+ const [files] = await bucket.getFiles();
38
+
39
+ const result: CleanupResult = {
40
+ totalProcessed: files.length,
41
+ deleted: 0,
42
+ preserved: 0,
43
+ errors: [],
44
+ };
45
+
46
+ for (const file of files) {
47
+ try {
48
+ await file.delete();
49
+ result.deleted++;
50
+ onProgress?.(result.deleted, files.length);
51
+ } catch (error) {
52
+ result.errors.push(`Failed to delete ${file.name}: ${error}`);
53
+ }
54
+ }
55
+
56
+ return result;
57
+ }
58
+
59
+ /**
60
+ * Delete files by prefix (folder)
61
+ */
62
+ export async function deleteFilesByPrefix(
63
+ storage: admin.storage.Storage,
64
+ prefix: string,
65
+ onProgress?: (deleted: number, total: number) => void
66
+ ): Promise<CleanupResult> {
67
+ const bucket = storage.bucket();
68
+ const [files] = await bucket.getFiles({ prefix });
69
+
70
+ const result: CleanupResult = {
71
+ totalProcessed: files.length,
72
+ deleted: 0,
73
+ preserved: 0,
74
+ errors: [],
75
+ };
76
+
77
+ for (const file of files) {
78
+ try {
79
+ await file.delete();
80
+ result.deleted++;
81
+ onProgress?.(result.deleted, files.length);
82
+ } catch (error) {
83
+ result.errors.push(`Failed to delete ${file.name}: ${error}`);
84
+ }
85
+ }
86
+
87
+ return result;
88
+ }
89
+
90
+ /**
91
+ * Get storage statistics
92
+ */
93
+ export async function getStorageStats(
94
+ storage: admin.storage.Storage
95
+ ): Promise<{
96
+ totalFiles: number;
97
+ totalSizeBytes: number;
98
+ totalSizeMB: number;
99
+ }> {
100
+ const files = await listFiles(storage);
101
+ const totalSizeBytes = files.reduce((sum, file) => sum + file.size, 0);
102
+
103
+ return {
104
+ totalFiles: files.length,
105
+ totalSizeBytes,
106
+ totalSizeMB: Math.round((totalSizeBytes / (1024 * 1024)) * 100) / 100,
107
+ };
108
+ }
109
+
110
+ /**
111
+ * Delete user files (files in users/{userId}/ folder)
112
+ */
113
+ export async function deleteUserFiles(
114
+ storage: admin.storage.Storage,
115
+ userId: string,
116
+ onProgress?: (deleted: number, total: number) => void
117
+ ): Promise<CleanupResult> {
118
+ return deleteFilesByPrefix(storage, `users/${userId}/`, onProgress);
119
+ }
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Firebase Admin Scripts - Shared Types
3
+ * Generic types for Firebase Admin operations
4
+ */
5
+
6
+ export interface FirebaseAdminConfig {
7
+ /** Path to service account JSON file */
8
+ serviceAccountPath: string;
9
+ /** Firebase project ID */
10
+ projectId: string;
11
+ /** Storage bucket name (optional) */
12
+ storageBucket?: string;
13
+ }
14
+
15
+ export interface CollectionInfo {
16
+ name: string;
17
+ documentCount: number;
18
+ sampleDocumentId?: string;
19
+ hasSubcollections?: boolean;
20
+ }
21
+
22
+ export interface UserInfo {
23
+ uid: string;
24
+ email?: string;
25
+ displayName?: string;
26
+ isAnonymous: boolean;
27
+ createdAt?: Date;
28
+ providerCount: number;
29
+ }
30
+
31
+ export interface CleanupResult {
32
+ totalProcessed: number;
33
+ deleted: number;
34
+ preserved: number;
35
+ errors: string[];
36
+ }
37
+
38
+ export interface BatchResult {
39
+ success: boolean;
40
+ processed: number;
41
+ errors: string[];
42
+ }
43
+
44
+ export interface StorageFileInfo {
45
+ name: string;
46
+ size: number;
47
+ contentType?: string;
48
+ createdAt?: Date;
49
+ }
50
+
51
+ export interface ResetSummary {
52
+ authUsersDeleted: number;
53
+ firestoreDocsDeleted: number;
54
+ storageFilesDeleted: number;
55
+ }
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Firebase Admin Scripts - Utility Functions
3
+ * Generic helpers for seeding and testing
4
+ */
5
+
6
+ /**
7
+ * Generate random ID
8
+ */
9
+ export function randomId(): string {
10
+ return (
11
+ Math.random().toString(36).substring(2, 15) +
12
+ Math.random().toString(36).substring(2, 15)
13
+ );
14
+ }
15
+
16
+ /**
17
+ * Generate random date within the past N days
18
+ */
19
+ export function randomDate(daysAgo: number): Date {
20
+ const now = new Date();
21
+ const pastDate = new Date(now.getTime() - daysAgo * 24 * 60 * 60 * 1000);
22
+ const randomTime =
23
+ pastDate.getTime() + Math.random() * (now.getTime() - pastDate.getTime());
24
+ return new Date(randomTime);
25
+ }
26
+
27
+ /**
28
+ * Get random item from array
29
+ */
30
+ export function randomItem<T>(arr: T[]): T {
31
+ return arr[Math.floor(Math.random() * arr.length)];
32
+ }
33
+
34
+ /**
35
+ * Generate random number in range
36
+ */
37
+ export function randomNumber(min: number, max: number): number {
38
+ return Math.floor(Math.random() * (max - min + 1)) + min;
39
+ }
40
+
41
+ /**
42
+ * Generate random boolean
43
+ */
44
+ export function randomBoolean(): boolean {
45
+ return Math.random() > 0.5;
46
+ }
47
+
48
+ /**
49
+ * Sleep for specified milliseconds
50
+ */
51
+ export function sleep(ms: number): Promise<void> {
52
+ return new Promise((resolve) => setTimeout(resolve, ms));
53
+ }
54
+
55
+ /**
56
+ * Format bytes to human readable string
57
+ */
58
+ export function formatBytes(bytes: number): string {
59
+ if (bytes === 0) return "0 Bytes";
60
+ const k = 1024;
61
+ const sizes = ["Bytes", "KB", "MB", "GB"];
62
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
63
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
64
+ }
65
+
66
+ /**
67
+ * Create a confirmation prompt (for CLI scripts)
68
+ */
69
+ export function createConfirmationTimer(
70
+ seconds: number,
71
+ warningMessage: string
72
+ ): Promise<void> {
73
+ return new Promise((resolve) => {
74
+ console.log(warningMessage);
75
+ console.log(`\nPress Ctrl+C to cancel, or wait ${seconds} seconds to continue...\n`);
76
+ setTimeout(resolve, seconds * 1000);
77
+ });
78
+ }
79
+
80
+ /**
81
+ * Print separator line
82
+ */
83
+ export function printSeparator(char = "=", length = 70): void {
84
+ console.log(char.repeat(length));
85
+ }
86
+
87
+ /**
88
+ * Print header with separators
89
+ */
90
+ export function printHeader(title: string): void {
91
+ printSeparator();
92
+ console.log(title);
93
+ printSeparator();
94
+ console.log();
95
+ }