@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/firestore.ts
CHANGED
|
@@ -3,278 +3,23 @@
|
|
|
3
3
|
* Generic Firestore operations for admin scripts
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
}
|
|
6
|
+
// Query functions
|
|
7
|
+
export {
|
|
8
|
+
listCollections,
|
|
9
|
+
listUserSubcollections,
|
|
10
|
+
countDocuments,
|
|
11
|
+
getUserStats,
|
|
12
|
+
} from "./firestore-queries";
|
|
13
|
+
|
|
14
|
+
// Delete operations
|
|
15
|
+
export {
|
|
16
|
+
deleteCollection,
|
|
17
|
+
deleteUserSubcollection,
|
|
18
|
+
deleteAllData,
|
|
19
|
+
} from "./firestore-operations";
|
|
20
|
+
|
|
21
|
+
// Seed operations
|
|
22
|
+
export {
|
|
23
|
+
seedBatch,
|
|
24
|
+
seedUserSubcollection,
|
|
25
|
+
} from "./firestore-seeding";
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Firebase Admin User Commands
|
|
3
|
+
* Command functions for modifying user data
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as admin from "firebase-admin";
|
|
7
|
+
import type { UserCredits, CreditsConfig } from "./types";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Initialize credits for a user
|
|
11
|
+
*/
|
|
12
|
+
export async function initializeUserCredits(
|
|
13
|
+
db: admin.firestore.Firestore,
|
|
14
|
+
userId: string,
|
|
15
|
+
config: CreditsConfig
|
|
16
|
+
): Promise<UserCredits> {
|
|
17
|
+
const { collectionName = "user_credits", textLimit = 0, imageLimit = 0 } = config;
|
|
18
|
+
|
|
19
|
+
const now = admin.firestore.FieldValue.serverTimestamp();
|
|
20
|
+
|
|
21
|
+
const credits: Omit<UserCredits, "createdAt" | "updatedAt"> & {
|
|
22
|
+
createdAt: admin.firestore.FieldValue;
|
|
23
|
+
updatedAt: admin.firestore.FieldValue;
|
|
24
|
+
} = {
|
|
25
|
+
text: textLimit,
|
|
26
|
+
image: imageLimit,
|
|
27
|
+
video: 0,
|
|
28
|
+
audio: 0,
|
|
29
|
+
createdAt: now,
|
|
30
|
+
updatedAt: now,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
await db.collection(collectionName).doc(userId).set(credits, { merge: true });
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
text: textLimit,
|
|
37
|
+
image: imageLimit,
|
|
38
|
+
video: 0,
|
|
39
|
+
audio: 0,
|
|
40
|
+
createdAt: new Date(),
|
|
41
|
+
updatedAt: new Date(),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Add credits to a user
|
|
47
|
+
*/
|
|
48
|
+
export async function addUserCredits(
|
|
49
|
+
db: admin.firestore.Firestore,
|
|
50
|
+
userId: string,
|
|
51
|
+
credits: { text?: number; image?: number; video?: number; audio?: number },
|
|
52
|
+
collectionName = "user_credits"
|
|
53
|
+
): Promise<void> {
|
|
54
|
+
const updates: Record<string, admin.firestore.FieldValue> = {
|
|
55
|
+
updatedAt: admin.firestore.FieldValue.serverTimestamp(),
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
if (credits.text) {
|
|
59
|
+
updates.text = admin.firestore.FieldValue.increment(credits.text);
|
|
60
|
+
}
|
|
61
|
+
if (credits.image) {
|
|
62
|
+
updates.image = admin.firestore.FieldValue.increment(credits.image);
|
|
63
|
+
}
|
|
64
|
+
if (credits.video) {
|
|
65
|
+
updates.video = admin.firestore.FieldValue.increment(credits.video);
|
|
66
|
+
}
|
|
67
|
+
if (credits.audio) {
|
|
68
|
+
updates.audio = admin.firestore.FieldValue.increment(credits.audio);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
await db.collection(collectionName).doc(userId).update(updates);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Set credits for a user (overwrite)
|
|
76
|
+
*/
|
|
77
|
+
export async function setUserCredits(
|
|
78
|
+
db: admin.firestore.Firestore,
|
|
79
|
+
userId: string,
|
|
80
|
+
credits: { text?: number; image?: number; video?: number; audio?: number },
|
|
81
|
+
collectionName = "user_credits"
|
|
82
|
+
): Promise<void> {
|
|
83
|
+
const updates: Record<string, unknown> = {
|
|
84
|
+
updatedAt: admin.firestore.FieldValue.serverTimestamp(),
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
if (credits.text !== undefined) updates.text = credits.text;
|
|
88
|
+
if (credits.image !== undefined) updates.image = credits.image;
|
|
89
|
+
if (credits.video !== undefined) updates.video = credits.video;
|
|
90
|
+
if (credits.audio !== undefined) updates.audio = credits.audio;
|
|
91
|
+
|
|
92
|
+
await db.collection(collectionName).doc(userId).set(updates, { merge: true });
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Delete user credits document
|
|
97
|
+
*/
|
|
98
|
+
export async function deleteUserCredits(
|
|
99
|
+
db: admin.firestore.Firestore,
|
|
100
|
+
userId: string,
|
|
101
|
+
collectionName = "user_credits"
|
|
102
|
+
): Promise<void> {
|
|
103
|
+
await db.collection(collectionName).doc(userId).delete();
|
|
104
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Firebase Admin User Formatters
|
|
3
|
+
* Format functions for displaying user data
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { UserData } from "./types";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Print user data in formatted way
|
|
10
|
+
*/
|
|
11
|
+
export function printUserData(data: UserData): void {
|
|
12
|
+
console.log("\n" + "═".repeat(60));
|
|
13
|
+
console.log(`👤 USER: ${data.userId}`);
|
|
14
|
+
console.log("═".repeat(60));
|
|
15
|
+
|
|
16
|
+
console.log("\n📋 PROFILE:");
|
|
17
|
+
if (data.profile) {
|
|
18
|
+
console.log(JSON.stringify(data.profile, null, 2));
|
|
19
|
+
} else {
|
|
20
|
+
console.log(" ❌ Not found");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
console.log("\n💰 CREDITS:");
|
|
24
|
+
if (data.credits) {
|
|
25
|
+
console.log(` Text: ${data.credits.text || 0}`);
|
|
26
|
+
console.log(` Image: ${data.credits.image || 0}`);
|
|
27
|
+
console.log(` Video: ${data.credits.video || 0}`);
|
|
28
|
+
console.log(` Audio: ${data.credits.audio || 0}`);
|
|
29
|
+
} else {
|
|
30
|
+
console.log(" ❌ Not found");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
console.log("\n🔔 SUBSCRIPTIONS:");
|
|
34
|
+
if (data.subscriptions.length > 0) {
|
|
35
|
+
data.subscriptions.forEach((sub) => {
|
|
36
|
+
console.log(` - ${sub.id}: ${JSON.stringify(sub)}`);
|
|
37
|
+
});
|
|
38
|
+
} else {
|
|
39
|
+
console.log(" ❌ None");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
console.log("\n🧾 TRANSACTIONS:");
|
|
43
|
+
if (data.transactions.length > 0) {
|
|
44
|
+
data.transactions.slice(0, 5).forEach((tx) => {
|
|
45
|
+
console.log(` - ${tx.id}: ${JSON.stringify(tx)}`);
|
|
46
|
+
});
|
|
47
|
+
if (data.transactions.length > 5) {
|
|
48
|
+
console.log(` ... and ${data.transactions.length - 5} more`);
|
|
49
|
+
}
|
|
50
|
+
} else {
|
|
51
|
+
console.log(" ❌ None");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
console.log("\n" + "═".repeat(60) + "\n");
|
|
55
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Firebase Admin User Queries
|
|
3
|
+
* Query functions for reading user data
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as admin from "firebase-admin";
|
|
7
|
+
import type { UserData, UserCredits } from "./types";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Get complete user data including profile and all related collections
|
|
11
|
+
*/
|
|
12
|
+
export async function getUserData(
|
|
13
|
+
db: admin.firestore.Firestore,
|
|
14
|
+
userId: string,
|
|
15
|
+
options?: {
|
|
16
|
+
includeCredits?: boolean;
|
|
17
|
+
includeSubscriptions?: boolean;
|
|
18
|
+
includeTransactions?: boolean;
|
|
19
|
+
creditsCollection?: string;
|
|
20
|
+
}
|
|
21
|
+
): Promise<UserData> {
|
|
22
|
+
const {
|
|
23
|
+
includeCredits = true,
|
|
24
|
+
includeSubscriptions = true,
|
|
25
|
+
includeTransactions = true,
|
|
26
|
+
creditsCollection = "user_credits",
|
|
27
|
+
} = options || {};
|
|
28
|
+
|
|
29
|
+
const result: UserData = {
|
|
30
|
+
userId,
|
|
31
|
+
exists: false,
|
|
32
|
+
profile: null,
|
|
33
|
+
credits: null,
|
|
34
|
+
subscriptions: [],
|
|
35
|
+
transactions: [],
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// Get user profile
|
|
39
|
+
const userDoc = await db.collection("users").doc(userId).get();
|
|
40
|
+
if (userDoc.exists) {
|
|
41
|
+
result.exists = true;
|
|
42
|
+
result.profile = userDoc.data() as Record<string, unknown>;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Get credits from root-level collection
|
|
46
|
+
if (includeCredits) {
|
|
47
|
+
const creditsDoc = await db.collection(creditsCollection).doc(userId).get();
|
|
48
|
+
if (creditsDoc.exists) {
|
|
49
|
+
result.credits = creditsDoc.data() as UserCredits;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Get subscriptions subcollection
|
|
54
|
+
if (includeSubscriptions) {
|
|
55
|
+
const subsSnapshot = await db
|
|
56
|
+
.collection("users")
|
|
57
|
+
.doc(userId)
|
|
58
|
+
.collection("subscriptions")
|
|
59
|
+
.get();
|
|
60
|
+
result.subscriptions = subsSnapshot.docs.map((doc) => ({
|
|
61
|
+
id: doc.id,
|
|
62
|
+
...doc.data(),
|
|
63
|
+
}));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Get transactions subcollection
|
|
67
|
+
if (includeTransactions) {
|
|
68
|
+
const txSnapshot = await db
|
|
69
|
+
.collection("users")
|
|
70
|
+
.doc(userId)
|
|
71
|
+
.collection("transactions")
|
|
72
|
+
.orderBy("createdAt", "desc")
|
|
73
|
+
.limit(50)
|
|
74
|
+
.get();
|
|
75
|
+
result.transactions = txSnapshot.docs.map((doc) => ({
|
|
76
|
+
id: doc.id,
|
|
77
|
+
...doc.data(),
|
|
78
|
+
}));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return result;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* List all users with their credit balances
|
|
86
|
+
*/
|
|
87
|
+
export async function listUsersWithCredits(
|
|
88
|
+
db: admin.firestore.Firestore,
|
|
89
|
+
options?: {
|
|
90
|
+
creditsCollection?: string;
|
|
91
|
+
limit?: number;
|
|
92
|
+
onlyWithCredits?: boolean;
|
|
93
|
+
}
|
|
94
|
+
): Promise<
|
|
95
|
+
Array<{
|
|
96
|
+
userId: string;
|
|
97
|
+
displayName?: string;
|
|
98
|
+
email?: string;
|
|
99
|
+
isAnonymous: boolean;
|
|
100
|
+
credits: UserCredits | null;
|
|
101
|
+
}>
|
|
102
|
+
> {
|
|
103
|
+
const { creditsCollection = "user_credits", limit = 100, onlyWithCredits = false } = options || {};
|
|
104
|
+
|
|
105
|
+
const usersSnapshot = await db.collection("users").limit(limit).get();
|
|
106
|
+
const result: Array<{
|
|
107
|
+
userId: string;
|
|
108
|
+
displayName?: string;
|
|
109
|
+
email?: string;
|
|
110
|
+
isAnonymous: boolean;
|
|
111
|
+
credits: UserCredits | null;
|
|
112
|
+
}> = [];
|
|
113
|
+
|
|
114
|
+
for (const userDoc of usersSnapshot.docs) {
|
|
115
|
+
const userData = userDoc.data();
|
|
116
|
+
const creditsDoc = await db.collection(creditsCollection).doc(userDoc.id).get();
|
|
117
|
+
const credits = creditsDoc.exists ? (creditsDoc.data() as UserCredits) : null;
|
|
118
|
+
|
|
119
|
+
if (onlyWithCredits && !credits) continue;
|
|
120
|
+
|
|
121
|
+
result.push({
|
|
122
|
+
userId: userDoc.id,
|
|
123
|
+
displayName: userData.displayName,
|
|
124
|
+
email: userData.email,
|
|
125
|
+
isAnonymous: userData.isAnonymous || false,
|
|
126
|
+
credits,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return result;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Get credits summary across all users
|
|
135
|
+
*/
|
|
136
|
+
export async function getCreditsSummary(
|
|
137
|
+
db: admin.firestore.Firestore,
|
|
138
|
+
collectionName = "user_credits"
|
|
139
|
+
): Promise<{
|
|
140
|
+
totalUsers: number;
|
|
141
|
+
totalText: number;
|
|
142
|
+
totalImage: number;
|
|
143
|
+
totalVideo: number;
|
|
144
|
+
totalAudio: number;
|
|
145
|
+
usersWithCredits: number;
|
|
146
|
+
usersWithZeroCredits: number;
|
|
147
|
+
}> {
|
|
148
|
+
const snapshot = await db.collection(collectionName).get();
|
|
149
|
+
|
|
150
|
+
let totalText = 0;
|
|
151
|
+
let totalImage = 0;
|
|
152
|
+
let totalVideo = 0;
|
|
153
|
+
let totalAudio = 0;
|
|
154
|
+
let usersWithCredits = 0;
|
|
155
|
+
let usersWithZeroCredits = 0;
|
|
156
|
+
|
|
157
|
+
snapshot.docs.forEach((doc) => {
|
|
158
|
+
const data = doc.data();
|
|
159
|
+
const text = data.text || 0;
|
|
160
|
+
const image = data.image || 0;
|
|
161
|
+
const video = data.video || 0;
|
|
162
|
+
const audio = data.audio || 0;
|
|
163
|
+
|
|
164
|
+
totalText += text;
|
|
165
|
+
totalImage += image;
|
|
166
|
+
totalVideo += video;
|
|
167
|
+
totalAudio += audio;
|
|
168
|
+
|
|
169
|
+
if (text > 0 || image > 0 || video > 0 || audio > 0) {
|
|
170
|
+
usersWithCredits++;
|
|
171
|
+
} else {
|
|
172
|
+
usersWithZeroCredits++;
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
return {
|
|
177
|
+
totalUsers: snapshot.docs.length,
|
|
178
|
+
totalText,
|
|
179
|
+
totalImage,
|
|
180
|
+
totalVideo,
|
|
181
|
+
totalAudio,
|
|
182
|
+
usersWithCredits,
|
|
183
|
+
usersWithZeroCredits,
|
|
184
|
+
};
|
|
185
|
+
}
|