@umituz/react-native-firebase 1.13.48 → 1.13.50
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/dist/scripts/cli-handlers.d.ts +27 -0
- package/dist/scripts/cli-handlers.d.ts.map +1 -0
- package/dist/scripts/cli-handlers.js +125 -0
- package/dist/scripts/cli-handlers.js.map +1 -0
- package/dist/scripts/cli-parser.d.ts +25 -0
- package/dist/scripts/cli-parser.d.ts.map +1 -0
- package/dist/scripts/cli-parser.js +101 -0
- package/dist/scripts/cli-parser.js.map +1 -0
- package/dist/scripts/cli.js +20 -155
- package/dist/scripts/cli.js.map +1 -1
- package/dist/scripts/firestore-operations.d.ts +18 -0
- package/dist/scripts/firestore-operations.d.ts.map +1 -0
- package/dist/scripts/firestore-operations.js +88 -0
- package/dist/scripts/firestore-operations.js.map +1 -0
- package/dist/scripts/firestore-queries.d.ts +27 -0
- package/dist/scripts/firestore-queries.d.ts.map +1 -0
- package/dist/scripts/firestore-queries.js +77 -0
- package/dist/scripts/firestore-queries.js.map +1 -0
- package/dist/scripts/firestore-seeding.d.ts +21 -0
- package/dist/scripts/firestore-seeding.d.ts.map +1 -0
- package/dist/scripts/firestore-seeding.js +67 -0
- package/dist/scripts/firestore-seeding.js.map +1 -0
- package/dist/scripts/firestore.d.ts +3 -48
- package/dist/scripts/firestore.d.ts.map +1 -1
- package/dist/scripts/firestore.js +16 -210
- package/dist/scripts/firestore.js.map +1 -1
- package/dist/scripts/user-commands.d.ts +33 -0
- package/dist/scripts/user-commands.d.ts.map +1 -0
- package/dist/scripts/user-commands.js +113 -0
- package/dist/scripts/user-commands.js.map +1 -0
- package/dist/scripts/user-formatters.d.ts +10 -0
- package/dist/scripts/user-formatters.d.ts.map +1 -0
- package/dist/scripts/user-formatters.js +55 -0
- package/dist/scripts/user-formatters.js.map +1 -0
- package/dist/scripts/user-queries.d.ts +42 -0
- package/dist/scripts/user-queries.d.ts.map +1 -0
- package/dist/scripts/user-queries.js +125 -0
- package/dist/scripts/user-queries.js.map +1 -0
- package/dist/scripts/user.d.ts +3 -67
- package/dist/scripts/user.d.ts.map +1 -1
- package/dist/scripts/user.js +15 -272
- package/dist/scripts/user.js.map +1 -1
- package/package.json +1 -1
- package/scripts/cli-handlers.ts +170 -0
- package/scripts/cli-parser.ts +82 -0
- package/scripts/cli.ts +27 -193
- package/scripts/firestore-operations.ts +111 -0
- package/scripts/firestore-queries.ts +97 -0
- package/scripts/firestore-seeding.ts +87 -0
- package/scripts/firestore.ts +20 -275
- package/scripts/user-commands.ts +104 -0
- package/scripts/user-formatters.ts +55 -0
- package/scripts/user-queries.ts +185 -0
- package/scripts/user.ts +19 -326
- package/src/auth/infrastructure/config/FirebaseAuthClient.ts +16 -171
- package/src/auth/infrastructure/services/account-deletion.service.ts +41 -351
- package/src/auth/infrastructure/services/reauthentication.service.ts +47 -207
- package/src/auth/infrastructure/services/reauthentication.types.ts +39 -0
package/scripts/cli.ts
CHANGED
|
@@ -22,90 +22,22 @@
|
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
24
|
import * as path from "path";
|
|
25
|
-
import * as fs from "fs";
|
|
26
25
|
import {
|
|
27
26
|
initFirebaseAdmin,
|
|
28
27
|
getFirestoreAdmin,
|
|
29
28
|
resetFirebaseAdmin,
|
|
30
29
|
} from "./init";
|
|
30
|
+
import { parseArgs, getProjectId } from "./cli-parser";
|
|
31
31
|
import {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
interface CLIOptions {
|
|
42
|
-
serviceAccountPath: string;
|
|
43
|
-
projectId?: string;
|
|
44
|
-
creditsCollection: string;
|
|
45
|
-
textLimit: number;
|
|
46
|
-
imageLimit: number;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function parseArgs(): { command: string; args: string[]; options: CLIOptions } {
|
|
50
|
-
const args = process.argv.slice(2);
|
|
51
|
-
const command = args[0] || "help";
|
|
52
|
-
const commandArgs: string[] = [];
|
|
53
|
-
const options: CLIOptions = {
|
|
54
|
-
serviceAccountPath: "./firebase-service-account.json",
|
|
55
|
-
creditsCollection: "user_credits",
|
|
56
|
-
textLimit: 100,
|
|
57
|
-
imageLimit: 100,
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
let i = 1;
|
|
61
|
-
while (i < args.length) {
|
|
62
|
-
const arg = args[i];
|
|
63
|
-
if (arg.startsWith("--")) {
|
|
64
|
-
const key = arg.slice(2);
|
|
65
|
-
const value = args[++i];
|
|
66
|
-
switch (key) {
|
|
67
|
-
case "service-account":
|
|
68
|
-
options.serviceAccountPath = value;
|
|
69
|
-
break;
|
|
70
|
-
case "project-id":
|
|
71
|
-
options.projectId = value;
|
|
72
|
-
break;
|
|
73
|
-
case "credits-collection":
|
|
74
|
-
options.creditsCollection = value;
|
|
75
|
-
break;
|
|
76
|
-
case "text-limit":
|
|
77
|
-
options.textLimit = parseInt(value) || 100;
|
|
78
|
-
break;
|
|
79
|
-
case "image-limit":
|
|
80
|
-
options.imageLimit = parseInt(value) || 100;
|
|
81
|
-
break;
|
|
82
|
-
}
|
|
83
|
-
} else {
|
|
84
|
-
commandArgs.push(arg);
|
|
85
|
-
}
|
|
86
|
-
i++;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
return { command, args: commandArgs, options };
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function getProjectId(options: CLIOptions): string {
|
|
93
|
-
if (options.projectId) return options.projectId;
|
|
94
|
-
|
|
95
|
-
const saPath = path.resolve(process.cwd(), options.serviceAccountPath);
|
|
96
|
-
if (fs.existsSync(saPath)) {
|
|
97
|
-
const sa = JSON.parse(fs.readFileSync(saPath, "utf8"));
|
|
98
|
-
return sa.project_id;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
throw new Error("Project ID not found. Use --project-id or ensure service account file exists.");
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
async function main() {
|
|
105
|
-
const { command, args, options } = parseArgs();
|
|
106
|
-
|
|
107
|
-
if (command === "help") {
|
|
108
|
-
console.log(`
|
|
32
|
+
handleReadUser,
|
|
33
|
+
handleInitCredits,
|
|
34
|
+
handleSetCredits,
|
|
35
|
+
handleListUsers,
|
|
36
|
+
handleCreditsSummary,
|
|
37
|
+
} from "./cli-handlers";
|
|
38
|
+
|
|
39
|
+
function printHelp(): void {
|
|
40
|
+
console.log(`
|
|
109
41
|
Firebase Admin CLI
|
|
110
42
|
|
|
111
43
|
Commands:
|
|
@@ -129,6 +61,13 @@ Examples:
|
|
|
129
61
|
npx ts-node cli.ts list-users --limit 50
|
|
130
62
|
npx ts-node cli.ts credits-summary
|
|
131
63
|
`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function main() {
|
|
67
|
+
const { command, args, options } = parseArgs();
|
|
68
|
+
|
|
69
|
+
if (command === "help") {
|
|
70
|
+
printHelp();
|
|
132
71
|
process.exit(0);
|
|
133
72
|
}
|
|
134
73
|
|
|
@@ -144,130 +83,25 @@ Examples:
|
|
|
144
83
|
const db = getFirestoreAdmin(app);
|
|
145
84
|
|
|
146
85
|
switch (command) {
|
|
147
|
-
case "read-user":
|
|
148
|
-
|
|
149
|
-
if (!userId) {
|
|
150
|
-
console.error("❌ Error: userId is required");
|
|
151
|
-
process.exit(1);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
printHeader(`📖 READ USER: ${userId}`);
|
|
155
|
-
const userData = await getUserData(db, userId, {
|
|
156
|
-
creditsCollection: options.creditsCollection,
|
|
157
|
-
});
|
|
158
|
-
printUserData(userData);
|
|
86
|
+
case "read-user":
|
|
87
|
+
await handleReadUser(db, args, options);
|
|
159
88
|
break;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
case "init-credits": {
|
|
163
|
-
const userId = args[0];
|
|
164
|
-
if (!userId) {
|
|
165
|
-
console.error("❌ Error: userId is required");
|
|
166
|
-
process.exit(1);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
printHeader(`💰 INIT CREDITS: ${userId}`);
|
|
170
|
-
console.log(`Collection: ${options.creditsCollection}`);
|
|
171
|
-
console.log(`Limits: Text=${options.textLimit}, Image=${options.imageLimit}\n`);
|
|
172
|
-
|
|
173
|
-
const existing = await getUserData(db, userId, {
|
|
174
|
-
creditsCollection: options.creditsCollection,
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
if (existing.credits) {
|
|
178
|
-
console.log("⚠️ Credits already exist:");
|
|
179
|
-
console.log(` Text: ${existing.credits.text}`);
|
|
180
|
-
console.log(` Image: ${existing.credits.image}`);
|
|
181
|
-
console.log("\n Use 'set-credits' to overwrite.");
|
|
182
|
-
break;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
const credits = await initializeUserCredits(db, userId, {
|
|
186
|
-
collectionName: options.creditsCollection,
|
|
187
|
-
textLimit: options.textLimit,
|
|
188
|
-
imageLimit: options.imageLimit,
|
|
189
|
-
});
|
|
190
89
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
console.log(` Image: ${credits.image}`);
|
|
90
|
+
case "init-credits":
|
|
91
|
+
await handleInitCredits(db, args, options);
|
|
194
92
|
break;
|
|
195
|
-
}
|
|
196
93
|
|
|
197
|
-
case "set-credits":
|
|
198
|
-
|
|
199
|
-
const text = parseInt(args[1]);
|
|
200
|
-
const image = parseInt(args[2]);
|
|
201
|
-
|
|
202
|
-
if (!userId || isNaN(text) || isNaN(image)) {
|
|
203
|
-
console.error("❌ Error: Usage: set-credits <userId> <text> <image>");
|
|
204
|
-
process.exit(1);
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
printHeader(`💰 SET CREDITS: ${userId}`);
|
|
208
|
-
console.log(`Setting: Text=${text}, Image=${image}\n`);
|
|
209
|
-
|
|
210
|
-
await setUserCredits(db, userId, { text, image }, options.creditsCollection);
|
|
211
|
-
|
|
212
|
-
console.log("✅ Credits set successfully!");
|
|
213
|
-
|
|
214
|
-
const updated = await getUserData(db, userId, {
|
|
215
|
-
creditsCollection: options.creditsCollection,
|
|
216
|
-
});
|
|
217
|
-
printUserData(updated);
|
|
94
|
+
case "set-credits":
|
|
95
|
+
await handleSetCredits(db, args, options);
|
|
218
96
|
break;
|
|
219
|
-
}
|
|
220
97
|
|
|
221
|
-
case "list-users":
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
printHeader("👥 USERS WITH CREDITS");
|
|
225
|
-
const users = await listUsersWithCredits(db, {
|
|
226
|
-
creditsCollection: options.creditsCollection,
|
|
227
|
-
limit,
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
console.log(`Found ${users.length} users:\n`);
|
|
231
|
-
printSeparator("-", 80);
|
|
232
|
-
console.log(
|
|
233
|
-
"ID".padEnd(30) +
|
|
234
|
-
"Name".padEnd(20) +
|
|
235
|
-
"Text".padEnd(10) +
|
|
236
|
-
"Image".padEnd(10) +
|
|
237
|
-
"Anon"
|
|
238
|
-
);
|
|
239
|
-
printSeparator("-", 80);
|
|
240
|
-
|
|
241
|
-
users.forEach((u) => {
|
|
242
|
-
const text = u.credits?.text ?? "-";
|
|
243
|
-
const image = u.credits?.image ?? "-";
|
|
244
|
-
console.log(
|
|
245
|
-
u.userId.substring(0, 28).padEnd(30) +
|
|
246
|
-
(u.displayName || "-").substring(0, 18).padEnd(20) +
|
|
247
|
-
String(text).padEnd(10) +
|
|
248
|
-
String(image).padEnd(10) +
|
|
249
|
-
(u.isAnonymous ? "Yes" : "No")
|
|
250
|
-
);
|
|
251
|
-
});
|
|
252
|
-
printSeparator("-", 80);
|
|
98
|
+
case "list-users":
|
|
99
|
+
await handleListUsers(db, args, options);
|
|
253
100
|
break;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
case "credits-summary": {
|
|
257
|
-
printHeader("📊 CREDITS SUMMARY");
|
|
258
|
-
const summary = await getCreditsSummary(db, options.creditsCollection);
|
|
259
101
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
console.log(`Zero Credits: ${summary.usersWithZeroCredits}`);
|
|
263
|
-
console.log();
|
|
264
|
-
console.log("Total Credits Across All Users:");
|
|
265
|
-
console.log(` Text: ${summary.totalText}`);
|
|
266
|
-
console.log(` Image: ${summary.totalImage}`);
|
|
267
|
-
console.log(` Video: ${summary.totalVideo}`);
|
|
268
|
-
console.log(` Audio: ${summary.totalAudio}`);
|
|
102
|
+
case "credits-summary":
|
|
103
|
+
await handleCreditsSummary(db, options);
|
|
269
104
|
break;
|
|
270
|
-
}
|
|
271
105
|
|
|
272
106
|
default:
|
|
273
107
|
console.error(`❌ Unknown command: ${command}`);
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Firebase Admin Firestore Operations
|
|
3
|
+
* Delete operations for Firestore data
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as admin from "firebase-admin";
|
|
7
|
+
|
|
8
|
+
const BATCH_SIZE = 500;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Delete collection in batches
|
|
12
|
+
*/
|
|
13
|
+
export async function deleteCollection(
|
|
14
|
+
db: admin.firestore.Firestore,
|
|
15
|
+
collectionPath: string,
|
|
16
|
+
onProgress?: (deleted: number) => void
|
|
17
|
+
): Promise<number> {
|
|
18
|
+
let totalDeleted = 0;
|
|
19
|
+
let hasMore = true;
|
|
20
|
+
|
|
21
|
+
while (hasMore) {
|
|
22
|
+
const snapshot = await db
|
|
23
|
+
.collection(collectionPath)
|
|
24
|
+
.orderBy("__name__")
|
|
25
|
+
.limit(BATCH_SIZE)
|
|
26
|
+
.get();
|
|
27
|
+
|
|
28
|
+
if (snapshot.empty) {
|
|
29
|
+
hasMore = false;
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const batch = db.batch();
|
|
34
|
+
snapshot.docs.forEach((doc) => batch.delete(doc.ref));
|
|
35
|
+
await batch.commit();
|
|
36
|
+
|
|
37
|
+
totalDeleted += snapshot.docs.length;
|
|
38
|
+
onProgress?.(totalDeleted);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return totalDeleted;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Delete user subcollection for all users
|
|
46
|
+
*/
|
|
47
|
+
export async function deleteUserSubcollection(
|
|
48
|
+
db: admin.firestore.Firestore,
|
|
49
|
+
subcollectionName: string,
|
|
50
|
+
onProgress?: (deleted: number) => void
|
|
51
|
+
): Promise<number> {
|
|
52
|
+
let totalDeleted = 0;
|
|
53
|
+
const usersSnapshot = await db.collection("users").get();
|
|
54
|
+
|
|
55
|
+
for (const userDoc of usersSnapshot.docs) {
|
|
56
|
+
const subcollectionRef = userDoc.ref.collection(subcollectionName);
|
|
57
|
+
const subcollectionSnapshot = await subcollectionRef.get();
|
|
58
|
+
|
|
59
|
+
if (!subcollectionSnapshot.empty) {
|
|
60
|
+
const batch = db.batch();
|
|
61
|
+
subcollectionSnapshot.docs.forEach((doc) => batch.delete(doc.ref));
|
|
62
|
+
await batch.commit();
|
|
63
|
+
totalDeleted += subcollectionSnapshot.docs.length;
|
|
64
|
+
onProgress?.(totalDeleted);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return totalDeleted;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Delete all Firestore data
|
|
73
|
+
*/
|
|
74
|
+
export async function deleteAllData(
|
|
75
|
+
db: admin.firestore.Firestore,
|
|
76
|
+
onProgress?: (collection: string, deleted: number) => void
|
|
77
|
+
): Promise<number> {
|
|
78
|
+
let totalDeleted = 0;
|
|
79
|
+
const collections = await db.listCollections();
|
|
80
|
+
|
|
81
|
+
for (const collection of collections) {
|
|
82
|
+
const snapshot = await collection.get();
|
|
83
|
+
|
|
84
|
+
// Delete subcollections first for users collection
|
|
85
|
+
if (collection.id === "users") {
|
|
86
|
+
for (const doc of snapshot.docs) {
|
|
87
|
+
const subcollections = await doc.ref.listCollections();
|
|
88
|
+
for (const subcollection of subcollections) {
|
|
89
|
+
const subSnapshot = await subcollection.get();
|
|
90
|
+
if (!subSnapshot.empty) {
|
|
91
|
+
const batch = db.batch();
|
|
92
|
+
subSnapshot.docs.forEach((subDoc) => batch.delete(subDoc.ref));
|
|
93
|
+
await batch.commit();
|
|
94
|
+
totalDeleted += subSnapshot.docs.length;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Delete main collection documents
|
|
101
|
+
if (!snapshot.empty) {
|
|
102
|
+
const batch = db.batch();
|
|
103
|
+
snapshot.docs.forEach((doc) => batch.delete(doc.ref));
|
|
104
|
+
await batch.commit();
|
|
105
|
+
totalDeleted += snapshot.docs.length;
|
|
106
|
+
onProgress?.(collection.id, totalDeleted);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return totalDeleted;
|
|
111
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Firebase Admin Firestore Queries
|
|
3
|
+
* Query functions for reading Firestore data
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as admin from "firebase-admin";
|
|
7
|
+
import type { CollectionInfo } from "./types";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* List all root-level collections
|
|
11
|
+
*/
|
|
12
|
+
export async function listCollections(
|
|
13
|
+
db: admin.firestore.Firestore
|
|
14
|
+
): Promise<CollectionInfo[]> {
|
|
15
|
+
const collections = await db.listCollections();
|
|
16
|
+
const result: CollectionInfo[] = [];
|
|
17
|
+
|
|
18
|
+
for (const collection of collections) {
|
|
19
|
+
const snapshot = await collection.limit(1000).get();
|
|
20
|
+
const info: CollectionInfo = {
|
|
21
|
+
name: collection.id,
|
|
22
|
+
documentCount: snapshot.docs.length,
|
|
23
|
+
sampleDocumentId: snapshot.docs[0]?.id,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
if (!snapshot.empty) {
|
|
27
|
+
const subcollections = await snapshot.docs[0].ref.listCollections();
|
|
28
|
+
info.hasSubcollections = subcollections.length > 0;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
result.push(info);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return result.sort((a, b) => b.documentCount - a.documentCount);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* List subcollections for a user document
|
|
39
|
+
*/
|
|
40
|
+
export async function listUserSubcollections(
|
|
41
|
+
db: admin.firestore.Firestore,
|
|
42
|
+
userId: string
|
|
43
|
+
): Promise<CollectionInfo[]> {
|
|
44
|
+
const userRef = db.collection("users").doc(userId);
|
|
45
|
+
const subcollections = await userRef.listCollections();
|
|
46
|
+
const result: CollectionInfo[] = [];
|
|
47
|
+
|
|
48
|
+
for (const subcollection of subcollections) {
|
|
49
|
+
const count = await subcollection.count().get();
|
|
50
|
+
result.push({
|
|
51
|
+
name: subcollection.id,
|
|
52
|
+
documentCount: count.data().count,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return result;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Count documents in collection
|
|
61
|
+
*/
|
|
62
|
+
export async function countDocuments(
|
|
63
|
+
db: admin.firestore.Firestore,
|
|
64
|
+
collectionPath: string
|
|
65
|
+
): Promise<number> {
|
|
66
|
+
const count = await db.collection(collectionPath).count().get();
|
|
67
|
+
return count.data().count;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Get user document count statistics
|
|
72
|
+
*/
|
|
73
|
+
export async function getUserStats(db: admin.firestore.Firestore): Promise<{
|
|
74
|
+
total: number;
|
|
75
|
+
anonymous: number;
|
|
76
|
+
authenticated: number;
|
|
77
|
+
}> {
|
|
78
|
+
const usersSnapshot = await db.collection("users").get();
|
|
79
|
+
|
|
80
|
+
let anonymous = 0;
|
|
81
|
+
let authenticated = 0;
|
|
82
|
+
|
|
83
|
+
usersSnapshot.docs.forEach((doc) => {
|
|
84
|
+
const data = doc.data();
|
|
85
|
+
if (data.isAnonymous) {
|
|
86
|
+
anonymous++;
|
|
87
|
+
} else {
|
|
88
|
+
authenticated++;
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
total: usersSnapshot.docs.length,
|
|
94
|
+
anonymous,
|
|
95
|
+
authenticated,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Firebase Admin Firestore Seeding
|
|
3
|
+
* Seed operations for Firestore data
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as admin from "firebase-admin";
|
|
7
|
+
import type { BatchResult } from "./types";
|
|
8
|
+
|
|
9
|
+
const BATCH_SIZE = 500;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Seed documents in batches
|
|
13
|
+
*/
|
|
14
|
+
export async function seedBatch(
|
|
15
|
+
db: admin.firestore.Firestore,
|
|
16
|
+
collectionPath: string,
|
|
17
|
+
docs: Array<{ id: string; data: Record<string, unknown> }>
|
|
18
|
+
): Promise<BatchResult> {
|
|
19
|
+
const result: BatchResult = {
|
|
20
|
+
success: true,
|
|
21
|
+
processed: 0,
|
|
22
|
+
errors: [],
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
for (let i = 0; i < docs.length; i += BATCH_SIZE) {
|
|
26
|
+
const batch = db.batch();
|
|
27
|
+
const slice = docs.slice(i, i + BATCH_SIZE);
|
|
28
|
+
|
|
29
|
+
for (const { id, data } of slice) {
|
|
30
|
+
const ref = db.collection(collectionPath).doc(id);
|
|
31
|
+
const clean = Object.fromEntries(
|
|
32
|
+
Object.entries(data).filter(([, v]) => v !== undefined)
|
|
33
|
+
);
|
|
34
|
+
batch.set(ref, clean);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
await batch.commit();
|
|
39
|
+
result.processed += slice.length;
|
|
40
|
+
} catch (error) {
|
|
41
|
+
result.success = false;
|
|
42
|
+
result.errors.push(`Batch failed at index ${i}: ${error}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return result;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Seed user subcollection
|
|
51
|
+
*/
|
|
52
|
+
export async function seedUserSubcollection(
|
|
53
|
+
db: admin.firestore.Firestore,
|
|
54
|
+
userId: string,
|
|
55
|
+
subcollectionName: string,
|
|
56
|
+
docs: Array<{ id: string; data: Record<string, unknown> }>
|
|
57
|
+
): Promise<BatchResult> {
|
|
58
|
+
const result: BatchResult = {
|
|
59
|
+
success: true,
|
|
60
|
+
processed: 0,
|
|
61
|
+
errors: [],
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const batch = db.batch();
|
|
65
|
+
|
|
66
|
+
for (const { id, data } of docs) {
|
|
67
|
+
const ref = db
|
|
68
|
+
.collection("users")
|
|
69
|
+
.doc(userId)
|
|
70
|
+
.collection(subcollectionName)
|
|
71
|
+
.doc(id);
|
|
72
|
+
const clean = Object.fromEntries(
|
|
73
|
+
Object.entries(data).filter(([, v]) => v !== undefined)
|
|
74
|
+
);
|
|
75
|
+
batch.set(ref, clean);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
await batch.commit();
|
|
80
|
+
result.processed = docs.length;
|
|
81
|
+
} catch (error) {
|
|
82
|
+
result.success = false;
|
|
83
|
+
result.errors.push(`Failed to seed subcollection: ${error}`);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return result;
|
|
87
|
+
}
|