react-firebase-ql 0.1.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/LICENSE +21 -0
- package/README.md +15 -0
- package/dist/index.d.ts +304 -0
- package/dist/index.js +1366 -0
- package/dist/index.mjs +1359 -0
- package/package.json +48 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,1359 @@
|
|
|
1
|
+
// src/Users.ts
|
|
2
|
+
import {
|
|
3
|
+
setPersistence,
|
|
4
|
+
browserSessionPersistence,
|
|
5
|
+
signInWithEmailAndPassword,
|
|
6
|
+
signInWithPhoneNumber,
|
|
7
|
+
updatePassword,
|
|
8
|
+
sendPasswordResetEmail,
|
|
9
|
+
signOut,
|
|
10
|
+
getMultiFactorResolver,
|
|
11
|
+
multiFactor,
|
|
12
|
+
PhoneAuthProvider,
|
|
13
|
+
PhoneMultiFactorGenerator,
|
|
14
|
+
createUserWithEmailAndPassword,
|
|
15
|
+
sendEmailVerification,
|
|
16
|
+
applyActionCode,
|
|
17
|
+
verifyPasswordResetCode,
|
|
18
|
+
deleteUser,
|
|
19
|
+
browserLocalPersistence,
|
|
20
|
+
confirmPasswordReset
|
|
21
|
+
} from "firebase/auth";
|
|
22
|
+
|
|
23
|
+
// src/BaseModel.ts
|
|
24
|
+
import {
|
|
25
|
+
doc,
|
|
26
|
+
collection,
|
|
27
|
+
onSnapshot,
|
|
28
|
+
where,
|
|
29
|
+
orderBy,
|
|
30
|
+
getDoc,
|
|
31
|
+
startAfter,
|
|
32
|
+
limit,
|
|
33
|
+
query,
|
|
34
|
+
updateDoc,
|
|
35
|
+
getDocs,
|
|
36
|
+
addDoc,
|
|
37
|
+
setDoc,
|
|
38
|
+
deleteDoc,
|
|
39
|
+
increment,
|
|
40
|
+
getCountFromServer,
|
|
41
|
+
writeBatch,
|
|
42
|
+
or,
|
|
43
|
+
and,
|
|
44
|
+
arrayUnion,
|
|
45
|
+
arrayRemove
|
|
46
|
+
} from "firebase/firestore";
|
|
47
|
+
|
|
48
|
+
// src/helpers.ts
|
|
49
|
+
var generateRandomString = (length) => {
|
|
50
|
+
if (length > 60) {
|
|
51
|
+
throw new Error(`Maximum generatable character is 60, ${length} was required`);
|
|
52
|
+
}
|
|
53
|
+
var result = "";
|
|
54
|
+
var characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
55
|
+
var charactersLength = characters.length;
|
|
56
|
+
for (var i = 0; i < length; i++) {
|
|
57
|
+
result += characters.charAt(Math.floor(Math.random() * charactersLength));
|
|
58
|
+
}
|
|
59
|
+
return result;
|
|
60
|
+
};
|
|
61
|
+
var camelToTitleCase = (key) => {
|
|
62
|
+
const topic = key[0].toUpperCase() + key.slice(1);
|
|
63
|
+
let result = "";
|
|
64
|
+
let i = 0;
|
|
65
|
+
while (i < topic.length) {
|
|
66
|
+
if (i === 0) {
|
|
67
|
+
result += topic.charAt(i);
|
|
68
|
+
} else {
|
|
69
|
+
if (topic.charAt(i) === topic.charAt(i).toUpperCase()) {
|
|
70
|
+
result += ` ${topic.charAt(i)}`;
|
|
71
|
+
} else {
|
|
72
|
+
result += topic.charAt(i);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
i++;
|
|
76
|
+
}
|
|
77
|
+
return result;
|
|
78
|
+
};
|
|
79
|
+
function convertUnicode(input) {
|
|
80
|
+
return input.replace(/\\+u([0-9a-fA-F]{4})/g, (a, b) => String.fromCharCode(parseInt(b, 16)));
|
|
81
|
+
}
|
|
82
|
+
var moneyFormatter = (x, shorten = true, decimailPlaces) => {
|
|
83
|
+
if (x === void 0) {
|
|
84
|
+
return "";
|
|
85
|
+
}
|
|
86
|
+
const base = 1e5;
|
|
87
|
+
const num = parseInt(x);
|
|
88
|
+
if (num < base || !shorten) {
|
|
89
|
+
return num.toFixed(decimailPlaces).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
|
90
|
+
} else {
|
|
91
|
+
let val = "";
|
|
92
|
+
if (num / base < 10) {
|
|
93
|
+
val = (num / 1e3).toFixed(decimailPlaces) + "K";
|
|
94
|
+
} else if (num < base * 1e4) {
|
|
95
|
+
val = (num / (base * 10)).toFixed(decimailPlaces) + "M";
|
|
96
|
+
} else {
|
|
97
|
+
val = (num / (base * 1e4)).toFixed(decimailPlaces) + "B";
|
|
98
|
+
}
|
|
99
|
+
return val;
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
function getDaysAgo(startDate, daysApart) {
|
|
103
|
+
const localCopy = new Date(startDate.toDateString());
|
|
104
|
+
return new Date(localCopy.setDate(localCopy.getDate() + daysApart));
|
|
105
|
+
}
|
|
106
|
+
function timeAgo(dateInput) {
|
|
107
|
+
const date = new Date(dateInput);
|
|
108
|
+
if (isNaN(date.getTime())) {
|
|
109
|
+
throw new Error("Invalid date input");
|
|
110
|
+
}
|
|
111
|
+
const now = /* @__PURE__ */ new Date();
|
|
112
|
+
const secondsAgo = Math.floor((now.getTime() - date.getTime()) / 1e3);
|
|
113
|
+
const minutesAgo = Math.floor(secondsAgo / 60);
|
|
114
|
+
if (minutesAgo < 1)
|
|
115
|
+
return "Just now";
|
|
116
|
+
const hoursAgo = Math.floor(minutesAgo / 60);
|
|
117
|
+
if (hoursAgo < 1)
|
|
118
|
+
return `${minutesAgo} minute${minutesAgo !== 1 ? "s" : ""} ago`;
|
|
119
|
+
const daysAgo = Math.floor(hoursAgo / 24);
|
|
120
|
+
if (daysAgo < 1)
|
|
121
|
+
return `${hoursAgo} hour${hoursAgo !== 1 ? "s" : ""} ago`;
|
|
122
|
+
const monthsAgo = Math.floor(daysAgo / 30);
|
|
123
|
+
if (daysAgo < 30)
|
|
124
|
+
return `${daysAgo} day${daysAgo !== 1 ? "s" : ""} ago`;
|
|
125
|
+
const yearsAgo = Math.floor(monthsAgo / 12);
|
|
126
|
+
if (monthsAgo < 12)
|
|
127
|
+
return `${monthsAgo} month${monthsAgo !== 1 ? "s" : ""} ago`;
|
|
128
|
+
return `${yearsAgo} year${yearsAgo !== 1 ? "s" : ""} ago`;
|
|
129
|
+
}
|
|
130
|
+
var range = (start, end) => Array.from({ length: end - start + 1 }, (_, i) => start + i);
|
|
131
|
+
var nairaFormatter = (amount) => {
|
|
132
|
+
return `\u20A6${moneyFormatter(amount, true, 2)}`;
|
|
133
|
+
};
|
|
134
|
+
var camelCaseToNormal = (camelCaseStr) => {
|
|
135
|
+
return camelCaseStr.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/^./, (str) => str.toUpperCase());
|
|
136
|
+
};
|
|
137
|
+
var errorLogger = (...error) => {
|
|
138
|
+
if (window.location.hostname === "localhost") {
|
|
139
|
+
console.log(error);
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
function isValidEmail(email) {
|
|
143
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
144
|
+
const pass = emailRegex.test(email);
|
|
145
|
+
if (!pass) {
|
|
146
|
+
return "Invalid email address";
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
function isValidPassword(password) {
|
|
150
|
+
if (password.length < 8) {
|
|
151
|
+
return "Password cannot be less than 8 characters in length";
|
|
152
|
+
}
|
|
153
|
+
const hasUppercase = /[A-Z]/.test(password);
|
|
154
|
+
if (!hasUppercase) {
|
|
155
|
+
return "Password must contain at least 1 uppercase letter";
|
|
156
|
+
}
|
|
157
|
+
const isAllUppercase = /^[A-Z]+$/.test(password);
|
|
158
|
+
if (isAllUppercase) {
|
|
159
|
+
return "Password cannot be all uppercase letter";
|
|
160
|
+
}
|
|
161
|
+
const hasSpecialCharacter = /[!@#$%^&*(),.?":{}|<>]/.test(password);
|
|
162
|
+
if (!hasSpecialCharacter) {
|
|
163
|
+
return "Password must contain at least 1 special character";
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
function numberToAlphabet(num) {
|
|
167
|
+
let result = "";
|
|
168
|
+
while (num > 0) {
|
|
169
|
+
num--;
|
|
170
|
+
const charCode = num % 26 + 65;
|
|
171
|
+
result = String.fromCharCode(charCode) + result;
|
|
172
|
+
num = Math.floor(num / 26);
|
|
173
|
+
}
|
|
174
|
+
return result;
|
|
175
|
+
}
|
|
176
|
+
var getNthNumberOfArray = (param) => {
|
|
177
|
+
const { num, step, start } = param;
|
|
178
|
+
if (num <= 0 || step <= 0) {
|
|
179
|
+
throw new Error("Both number and step must be greater than 0");
|
|
180
|
+
}
|
|
181
|
+
return Array.from({ length: Math.floor(num / step) }, (_, i) => {
|
|
182
|
+
if (start) {
|
|
183
|
+
i = start + i - 1;
|
|
184
|
+
return (i + 1) * step;
|
|
185
|
+
}
|
|
186
|
+
return (i + 1) * step;
|
|
187
|
+
});
|
|
188
|
+
};
|
|
189
|
+
var noEmptyField = (args) => {
|
|
190
|
+
return args.requiredFields.every(
|
|
191
|
+
(field) => Object.keys(FormData).includes(field) && args.formData[field] !== void 0 && args.formData[field] !== null && args.formData[field] !== ""
|
|
192
|
+
);
|
|
193
|
+
};
|
|
194
|
+
var getInitials = (fullName) => {
|
|
195
|
+
if (fullName) {
|
|
196
|
+
const names = fullName.trim().split(/\s+/);
|
|
197
|
+
if (names.length >= 2) {
|
|
198
|
+
const firstInitial = names[0].charAt(0).toUpperCase();
|
|
199
|
+
const lastInitial = names[names.length - 1].charAt(0).toUpperCase();
|
|
200
|
+
return `${firstInitial}${lastInitial}`;
|
|
201
|
+
}
|
|
202
|
+
if (names.length === 1) {
|
|
203
|
+
const name = names[0];
|
|
204
|
+
return `${name.charAt(0).toUpperCase()}${name.charAt(name.length - 1).toUpperCase()}`;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return "";
|
|
208
|
+
};
|
|
209
|
+
var fetchCurrentTimestamp = async () => {
|
|
210
|
+
const res = await fetch("https://worldtimeapi.org/api/timezone/etc/utc");
|
|
211
|
+
if (!res.ok)
|
|
212
|
+
throw new Error("Failed to fetch time");
|
|
213
|
+
const data = await res.json();
|
|
214
|
+
return {
|
|
215
|
+
iso: data.datetime,
|
|
216
|
+
unix: data.unixtime * 1e3
|
|
217
|
+
// Convert to milliseconds if needed
|
|
218
|
+
};
|
|
219
|
+
};
|
|
220
|
+
var formHasError = (formError) => {
|
|
221
|
+
return Object.values(formError).every((item) => item !== void 0);
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
// src/BaseModel.ts
|
|
225
|
+
var BaseModel = class {
|
|
226
|
+
// offset data
|
|
227
|
+
// offset?: QueryDocumentSnapshot<DocumentData>;
|
|
228
|
+
constructor(table, db) {
|
|
229
|
+
// Get a new write batch
|
|
230
|
+
// protected batch?: WriteBatch
|
|
231
|
+
// Database table name
|
|
232
|
+
this.table = "";
|
|
233
|
+
this.table = table;
|
|
234
|
+
this.firestorDB = db;
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* save multiple documents
|
|
238
|
+
* @param param0
|
|
239
|
+
*/
|
|
240
|
+
async saveBatch({ data }) {
|
|
241
|
+
try {
|
|
242
|
+
const batch = writeBatch(this.firestorDB);
|
|
243
|
+
const obj = data;
|
|
244
|
+
obj.forEach((document) => {
|
|
245
|
+
const docRef = document.reference ? doc(this.firestorDB, this.table, document.reference) : doc(collection(this.firestorDB, this.table));
|
|
246
|
+
if (document.reference) {
|
|
247
|
+
delete document.reference;
|
|
248
|
+
}
|
|
249
|
+
batch.set(docRef, document);
|
|
250
|
+
});
|
|
251
|
+
await batch.commit();
|
|
252
|
+
return true;
|
|
253
|
+
} catch (error) {
|
|
254
|
+
throw new Error(`saveBatch: , ${error}`);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* update multiple documents
|
|
259
|
+
* @param param0
|
|
260
|
+
*/
|
|
261
|
+
async updateBatch({ data }) {
|
|
262
|
+
try {
|
|
263
|
+
const batch = writeBatch(this.firestorDB);
|
|
264
|
+
const obj = data;
|
|
265
|
+
obj.forEach((document) => {
|
|
266
|
+
const docRef = doc(this.firestorDB, this.table, document.reference);
|
|
267
|
+
delete document.reference;
|
|
268
|
+
batch.update(docRef, document);
|
|
269
|
+
});
|
|
270
|
+
await batch.commit();
|
|
271
|
+
return true;
|
|
272
|
+
} catch (error) {
|
|
273
|
+
throw new Error(`updateBatch: , ${error}`);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* delete multiple documents
|
|
278
|
+
* @param param0
|
|
279
|
+
*/
|
|
280
|
+
async deleteBatch({ ids }) {
|
|
281
|
+
try {
|
|
282
|
+
const batch = writeBatch(this.firestorDB);
|
|
283
|
+
ids.forEach((id) => {
|
|
284
|
+
const docRef = doc(this.firestorDB, this.table, id);
|
|
285
|
+
batch.delete(docRef);
|
|
286
|
+
});
|
|
287
|
+
await batch.commit();
|
|
288
|
+
return true;
|
|
289
|
+
} catch (error) {
|
|
290
|
+
throw new Error(`deleteBatch: , ${error}`);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Get realtime update from the database
|
|
295
|
+
* @param id
|
|
296
|
+
*/
|
|
297
|
+
stream(callBack, id) {
|
|
298
|
+
const ref2 = id ? doc(this.firestorDB, this.table, id) : collection(this.firestorDB, this.table);
|
|
299
|
+
try {
|
|
300
|
+
if (id) {
|
|
301
|
+
return onSnapshot(ref2, (doc2) => {
|
|
302
|
+
callBack(doc2.exists() ? { ...doc2.data(), reference: ref2.id } : void 0);
|
|
303
|
+
});
|
|
304
|
+
} else {
|
|
305
|
+
return onSnapshot(ref2, (snapShot) => {
|
|
306
|
+
callBack(
|
|
307
|
+
snapShot.docs.map((value) => {
|
|
308
|
+
const data = { ...value.data(), reference: value.id };
|
|
309
|
+
return data;
|
|
310
|
+
})
|
|
311
|
+
);
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
} catch (error) {
|
|
315
|
+
callBack(void 0);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Get realtime value from database with where clause
|
|
320
|
+
* @param wh
|
|
321
|
+
* @param lim
|
|
322
|
+
* @param order
|
|
323
|
+
*/
|
|
324
|
+
streamWhere(wh, callBack, lim, order, offset) {
|
|
325
|
+
try {
|
|
326
|
+
const whereParameter = wh.map((clause) => where(
|
|
327
|
+
clause.key,
|
|
328
|
+
clause.operator,
|
|
329
|
+
clause.value
|
|
330
|
+
));
|
|
331
|
+
let constraint = [];
|
|
332
|
+
if (wh) {
|
|
333
|
+
constraint.push(...whereParameter);
|
|
334
|
+
}
|
|
335
|
+
if (order) {
|
|
336
|
+
constraint.push(orderBy(order.parameter, order.direction));
|
|
337
|
+
}
|
|
338
|
+
if (offset) {
|
|
339
|
+
getDoc(doc(this.firestorDB, this.table, offset)).then((off) => {
|
|
340
|
+
constraint.push(startAfter(off));
|
|
341
|
+
}).catch((error) => errorLogger(error));
|
|
342
|
+
}
|
|
343
|
+
if (lim) {
|
|
344
|
+
constraint.push(limit(lim));
|
|
345
|
+
}
|
|
346
|
+
const ref2 = collection(this.firestorDB, this.table);
|
|
347
|
+
return onSnapshot(query(
|
|
348
|
+
ref2,
|
|
349
|
+
...constraint
|
|
350
|
+
), (snapShot) => {
|
|
351
|
+
callBack(snapShot.docs.map((value) => {
|
|
352
|
+
const data = { ...value.data(), reference: value.id };
|
|
353
|
+
return data;
|
|
354
|
+
}));
|
|
355
|
+
});
|
|
356
|
+
} catch (error) {
|
|
357
|
+
throw new Error(`streamWhere: , ${error}`);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Fetch a single item from database
|
|
362
|
+
* @param id
|
|
363
|
+
*/
|
|
364
|
+
async find(id) {
|
|
365
|
+
try {
|
|
366
|
+
const ref2 = doc(this.firestorDB, this.table, id);
|
|
367
|
+
const docSnap = await getDoc(ref2);
|
|
368
|
+
if (docSnap.exists()) {
|
|
369
|
+
this.data = { ...docSnap.data(), reference: id };
|
|
370
|
+
return true;
|
|
371
|
+
}
|
|
372
|
+
return false;
|
|
373
|
+
} catch (error) {
|
|
374
|
+
throw new Error(`find: , ${error}`);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* check if data exists
|
|
379
|
+
* @param id
|
|
380
|
+
* @returns
|
|
381
|
+
*/
|
|
382
|
+
async dataExists(id) {
|
|
383
|
+
try {
|
|
384
|
+
const ref2 = doc(this.firestorDB, this.table, id);
|
|
385
|
+
const docSanp = await getDoc(ref2);
|
|
386
|
+
return docSanp.exists();
|
|
387
|
+
} catch (error) {
|
|
388
|
+
throw new Error(`dataExists: , ${error}`);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Update part of a data
|
|
393
|
+
* @param data
|
|
394
|
+
* @param id
|
|
395
|
+
* @returns
|
|
396
|
+
*/
|
|
397
|
+
async update(data, id) {
|
|
398
|
+
try {
|
|
399
|
+
delete data.reference;
|
|
400
|
+
const docRef = doc(this.firestorDB, this.table, id);
|
|
401
|
+
await updateDoc(docRef, data);
|
|
402
|
+
return true;
|
|
403
|
+
} catch (error) {
|
|
404
|
+
throw new Error(`update: , ${error}`);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
/**
|
|
408
|
+
* update an array in a document
|
|
409
|
+
* @param {Array<any>} data array of data to be saved
|
|
410
|
+
* @param {string} reference document reference
|
|
411
|
+
* @param {string} key document object key containing the array
|
|
412
|
+
* @returns {boolean}
|
|
413
|
+
*/
|
|
414
|
+
async updateAtomicArray(data, reference, key) {
|
|
415
|
+
try {
|
|
416
|
+
const docRef = doc(this.firestorDB, this.table, reference);
|
|
417
|
+
await updateDoc(docRef, { [key]: arrayUnion(...data) });
|
|
418
|
+
return true;
|
|
419
|
+
} catch (error) {
|
|
420
|
+
throw new Error(`updateAtomicArray error: ${error}`);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* removes items from document array
|
|
425
|
+
* @param {Array<any>} data array of data to be removed
|
|
426
|
+
* @param {string} id document reference
|
|
427
|
+
* @param {string} key key to reference
|
|
428
|
+
* @returns {boolean}
|
|
429
|
+
*/
|
|
430
|
+
async removeFromArray(data, id, key) {
|
|
431
|
+
try {
|
|
432
|
+
const docRef = doc(this.firestorDB, this.table, id);
|
|
433
|
+
await updateDoc(docRef, { [key]: arrayRemove(...data) });
|
|
434
|
+
return true;
|
|
435
|
+
} catch (error) {
|
|
436
|
+
throw new Error(`updateAtomicArray error: ${error}`);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* Get all items from database
|
|
441
|
+
* @returns void
|
|
442
|
+
*/
|
|
443
|
+
async findAll(ids) {
|
|
444
|
+
try {
|
|
445
|
+
const colRef = collection(this.firestorDB, this.table);
|
|
446
|
+
if (ids) {
|
|
447
|
+
const results = [];
|
|
448
|
+
for (let id of ids) {
|
|
449
|
+
const found = await this.find(id);
|
|
450
|
+
if (found) {
|
|
451
|
+
results.push(this.data);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
this.data = results;
|
|
455
|
+
return true;
|
|
456
|
+
} else {
|
|
457
|
+
const snaptshots = await getDocs(colRef);
|
|
458
|
+
if (!snaptshots.empty) {
|
|
459
|
+
this.data = snaptshots.docs.map((document) => {
|
|
460
|
+
return { ...document.data(), reference: document.id };
|
|
461
|
+
});
|
|
462
|
+
return true;
|
|
463
|
+
} else {
|
|
464
|
+
return false;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
} catch (error) {
|
|
468
|
+
throw new Error(`findAll: , ${error}`);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
/**
|
|
472
|
+
* perform complex query
|
|
473
|
+
* @param param0
|
|
474
|
+
*/
|
|
475
|
+
async findWhereOrAnd({ wh, lim, order, offset }) {
|
|
476
|
+
try {
|
|
477
|
+
const colRef = collection(this.firestorDB, this.table);
|
|
478
|
+
const andWhere = [];
|
|
479
|
+
const orWhere = [];
|
|
480
|
+
let filterConstraint = and();
|
|
481
|
+
if (wh) {
|
|
482
|
+
wh.parameter.forEach((clause) => {
|
|
483
|
+
const whe = where(
|
|
484
|
+
clause.key,
|
|
485
|
+
clause.operator,
|
|
486
|
+
clause.value
|
|
487
|
+
);
|
|
488
|
+
clause.type === "and" ? andWhere.push(whe) : orWhere.push(whe);
|
|
489
|
+
});
|
|
490
|
+
if (wh.type === "andOr") {
|
|
491
|
+
filterConstraint = and(...andWhere, or(...orWhere));
|
|
492
|
+
} else if (wh.type === "or") {
|
|
493
|
+
filterConstraint = or(...orWhere);
|
|
494
|
+
} else {
|
|
495
|
+
filterConstraint = and(...andWhere);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
let constraint = [];
|
|
499
|
+
if (order) {
|
|
500
|
+
constraint.push(orderBy(order.parameter, order.direction));
|
|
501
|
+
}
|
|
502
|
+
if (offset) {
|
|
503
|
+
const off = await getDoc(doc(this.firestorDB, this.table, offset));
|
|
504
|
+
constraint.push(startAfter(off));
|
|
505
|
+
}
|
|
506
|
+
if (lim) {
|
|
507
|
+
constraint.push(limit(lim));
|
|
508
|
+
}
|
|
509
|
+
const snapshot = await getDocs(
|
|
510
|
+
query(
|
|
511
|
+
colRef,
|
|
512
|
+
filterConstraint,
|
|
513
|
+
...constraint
|
|
514
|
+
)
|
|
515
|
+
);
|
|
516
|
+
if (!snapshot.empty) {
|
|
517
|
+
this.data = snapshot.docs.map((document) => {
|
|
518
|
+
return { ...document.data(), reference: document.id };
|
|
519
|
+
});
|
|
520
|
+
return true;
|
|
521
|
+
} else {
|
|
522
|
+
return false;
|
|
523
|
+
}
|
|
524
|
+
} catch (error) {
|
|
525
|
+
throw new Error(`findWhereOrAnd: ${error}`);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
/**
|
|
529
|
+
* complex query with and only
|
|
530
|
+
* @param param0
|
|
531
|
+
* @returns
|
|
532
|
+
*/
|
|
533
|
+
async findWhere({ wh, lim, order, offset }) {
|
|
534
|
+
try {
|
|
535
|
+
const whereParameter = wh ? wh.map((clause) => where(
|
|
536
|
+
clause.key,
|
|
537
|
+
clause.operator,
|
|
538
|
+
clause.value
|
|
539
|
+
)) : [];
|
|
540
|
+
let constraint = [];
|
|
541
|
+
if (wh) {
|
|
542
|
+
constraint.push(...whereParameter);
|
|
543
|
+
}
|
|
544
|
+
if (order) {
|
|
545
|
+
constraint.push(orderBy(order.parameter, order.direction));
|
|
546
|
+
}
|
|
547
|
+
if (offset) {
|
|
548
|
+
const off = await getDoc(doc(this.firestorDB, this.table, offset));
|
|
549
|
+
constraint.push(startAfter(off));
|
|
550
|
+
}
|
|
551
|
+
if (lim) {
|
|
552
|
+
constraint.push(limit(lim));
|
|
553
|
+
}
|
|
554
|
+
const ref2 = collection(this.firestorDB, this.table);
|
|
555
|
+
const snapshot = await getDocs(
|
|
556
|
+
query(
|
|
557
|
+
ref2,
|
|
558
|
+
...constraint
|
|
559
|
+
)
|
|
560
|
+
);
|
|
561
|
+
if (!snapshot.empty) {
|
|
562
|
+
return snapshot.docs.map((document) => {
|
|
563
|
+
return { ...document.data(), reference: document.id };
|
|
564
|
+
});
|
|
565
|
+
} else {
|
|
566
|
+
return [];
|
|
567
|
+
}
|
|
568
|
+
} catch (error) {
|
|
569
|
+
throw new Error(`findWhere: , ${error}`);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
/**
|
|
573
|
+
* create or update data
|
|
574
|
+
* @param data
|
|
575
|
+
*/
|
|
576
|
+
async save(data, id) {
|
|
577
|
+
delete data.reference;
|
|
578
|
+
try {
|
|
579
|
+
if (id === void 0) {
|
|
580
|
+
const documentRef = await addDoc(collection(this.firestorDB, this.table), data);
|
|
581
|
+
return documentRef.id;
|
|
582
|
+
} else {
|
|
583
|
+
await setDoc(doc(this.firestorDB, this.table, id), data);
|
|
584
|
+
return id;
|
|
585
|
+
}
|
|
586
|
+
} catch (error) {
|
|
587
|
+
throw new Error(`save error: , ${error}`);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
/**
|
|
591
|
+
* Delete document from database
|
|
592
|
+
* @param id
|
|
593
|
+
*/
|
|
594
|
+
async delete(id) {
|
|
595
|
+
try {
|
|
596
|
+
await deleteDoc(doc(this.firestorDB, this.table, id));
|
|
597
|
+
return true;
|
|
598
|
+
} catch (error) {
|
|
599
|
+
throw new Error(`delete: , ${error}`);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
/**
|
|
603
|
+
* Increment or decrement counters
|
|
604
|
+
* @param param0
|
|
605
|
+
*/
|
|
606
|
+
async incrementDecrement({ dbReference, key, isIncrement = true, incrementalValue }) {
|
|
607
|
+
try {
|
|
608
|
+
const docRef = doc(this.firestorDB, this.table, dbReference);
|
|
609
|
+
const value = isIncrement ? incrementalValue != null ? incrementalValue : 1 : (incrementalValue != null ? incrementalValue : 1) * -1;
|
|
610
|
+
await updateDoc(docRef, { [key]: increment(value) });
|
|
611
|
+
return true;
|
|
612
|
+
} catch (error) {
|
|
613
|
+
throw new Error(`incrementDecrement: , ${error}`);
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
/**
|
|
617
|
+
* Count data in database
|
|
618
|
+
* @param {whereClause[]} wh - query parameter (e.g. [
|
|
619
|
+
* {
|
|
620
|
+
*/
|
|
621
|
+
async countData(wh) {
|
|
622
|
+
try {
|
|
623
|
+
const qryParameter = wh.map((clause) => where(
|
|
624
|
+
clause.key,
|
|
625
|
+
clause.operator,
|
|
626
|
+
clause.value
|
|
627
|
+
));
|
|
628
|
+
const qry = query(
|
|
629
|
+
collection(this.firestorDB, this.table),
|
|
630
|
+
...qryParameter
|
|
631
|
+
);
|
|
632
|
+
const aggregate = await getCountFromServer(qry);
|
|
633
|
+
return aggregate.data().count;
|
|
634
|
+
} catch (error) {
|
|
635
|
+
throw new Error(`countData error: ,${error}`);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
/**
|
|
639
|
+
* Get realtime value from database with where clause
|
|
640
|
+
* @param wh
|
|
641
|
+
* @param lim
|
|
642
|
+
* @param order
|
|
643
|
+
*/
|
|
644
|
+
async streamCount(wh, callBack, order, offset) {
|
|
645
|
+
try {
|
|
646
|
+
const whereParameter = wh.map(
|
|
647
|
+
(clause) => where(clause.key, clause.operator, clause.value)
|
|
648
|
+
);
|
|
649
|
+
const constraint = [...whereParameter];
|
|
650
|
+
if (order)
|
|
651
|
+
constraint.push(orderBy(order.parameter, order.direction));
|
|
652
|
+
if (offset) {
|
|
653
|
+
const off = await getDoc(doc(this.firestorDB, this.table, offset));
|
|
654
|
+
constraint.push(startAfter(off));
|
|
655
|
+
}
|
|
656
|
+
const streamerConstraint = [...constraint, limit(1)];
|
|
657
|
+
const ref2 = collection(this.firestorDB, this.table);
|
|
658
|
+
const unsubscribe = onSnapshot(
|
|
659
|
+
query(ref2, ...streamerConstraint),
|
|
660
|
+
async (snapShot) => {
|
|
661
|
+
if (snapShot.empty)
|
|
662
|
+
return;
|
|
663
|
+
const aggregate = await getCountFromServer(query(
|
|
664
|
+
collection(this.firestorDB, this.table),
|
|
665
|
+
...constraint
|
|
666
|
+
));
|
|
667
|
+
callBack(aggregate.data().count);
|
|
668
|
+
}
|
|
669
|
+
);
|
|
670
|
+
return unsubscribe;
|
|
671
|
+
} catch (error) {
|
|
672
|
+
throw new Error(`streamCount: ${error}`);
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
};
|
|
676
|
+
|
|
677
|
+
// src/Users.ts
|
|
678
|
+
var Users = class extends BaseModel {
|
|
679
|
+
constructor() {
|
|
680
|
+
super(...arguments);
|
|
681
|
+
/**
|
|
682
|
+
* fetch phone number from database
|
|
683
|
+
* @param uid
|
|
684
|
+
* @returns
|
|
685
|
+
*/
|
|
686
|
+
this.getPhoneNumber = async (uid) => {
|
|
687
|
+
let phoneNumber = null;
|
|
688
|
+
const dbUser = await this.find(uid);
|
|
689
|
+
if (dbUser) {
|
|
690
|
+
const dUser = this.data;
|
|
691
|
+
phoneNumber = dUser.phoneNumber;
|
|
692
|
+
}
|
|
693
|
+
return phoneNumber;
|
|
694
|
+
};
|
|
695
|
+
/**
|
|
696
|
+
* send OTP to enable 2 factor authentication
|
|
697
|
+
* @param user
|
|
698
|
+
* @param recaptchaverifier
|
|
699
|
+
*/
|
|
700
|
+
this.setMultiFactorEnrollment = async (user, recaptchaverifier, auth, phoneNumber) => {
|
|
701
|
+
try {
|
|
702
|
+
const multifactorSession = await multiFactor(user).getSession();
|
|
703
|
+
const phoneInfoOptions = {
|
|
704
|
+
phoneNumber,
|
|
705
|
+
session: multifactorSession
|
|
706
|
+
};
|
|
707
|
+
const phoneAuthProvider = new PhoneAuthProvider(auth);
|
|
708
|
+
return await phoneAuthProvider.verifyPhoneNumber(phoneInfoOptions, recaptchaverifier);
|
|
709
|
+
} catch (e) {
|
|
710
|
+
throw new Error(`setMultiFactorEnrollment error: , ${e}`);
|
|
711
|
+
}
|
|
712
|
+
};
|
|
713
|
+
/**
|
|
714
|
+
* complete user signIn process with OTP
|
|
715
|
+
* @param param0
|
|
716
|
+
* @param userCode
|
|
717
|
+
* @param user
|
|
718
|
+
* @param onSuccess
|
|
719
|
+
* @returns {User | null}
|
|
720
|
+
*/
|
|
721
|
+
this.confirmOTP = async (verifier, userCode) => {
|
|
722
|
+
try {
|
|
723
|
+
const { verificationId, resolver } = verifier;
|
|
724
|
+
const cred = PhoneAuthProvider.credential(verificationId, userCode);
|
|
725
|
+
const multiFactorAssertion = PhoneMultiFactorGenerator.assertion(cred);
|
|
726
|
+
let user = null;
|
|
727
|
+
if (resolver) {
|
|
728
|
+
const credential = await resolver.resolveSignIn(multiFactorAssertion);
|
|
729
|
+
user = credential.user;
|
|
730
|
+
} else {
|
|
731
|
+
if (verifier.user) {
|
|
732
|
+
await multiFactor(verifier.user).enroll(multiFactorAssertion, "primary phone");
|
|
733
|
+
user = verifier.user;
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
return user;
|
|
737
|
+
} catch (error) {
|
|
738
|
+
throw new Error(`confirmOTP error: , ${error}`);
|
|
739
|
+
}
|
|
740
|
+
};
|
|
741
|
+
/**
|
|
742
|
+
* send OTP to member
|
|
743
|
+
* @param resolver
|
|
744
|
+
* @param recaptchaVerifier
|
|
745
|
+
* @param setter
|
|
746
|
+
* @returns {MFAVerifier | null}
|
|
747
|
+
*/
|
|
748
|
+
this.sendOTP = async (resolver, recaptchaVerifier, auth) => {
|
|
749
|
+
try {
|
|
750
|
+
if (resolver.hints[0].factorId === PhoneMultiFactorGenerator.FACTOR_ID) {
|
|
751
|
+
const phoneInfoOptions = {
|
|
752
|
+
multiFactorHint: resolver.hints[0],
|
|
753
|
+
session: resolver.session
|
|
754
|
+
};
|
|
755
|
+
const phoneAuthProvider = new PhoneAuthProvider(auth);
|
|
756
|
+
const verificationId = await phoneAuthProvider.verifyPhoneNumber(phoneInfoOptions, recaptchaVerifier);
|
|
757
|
+
return { verificationId, resolver };
|
|
758
|
+
} else {
|
|
759
|
+
return null;
|
|
760
|
+
}
|
|
761
|
+
} catch (e) {
|
|
762
|
+
throw new Error(`sendOTP error: , ${e}`);
|
|
763
|
+
}
|
|
764
|
+
};
|
|
765
|
+
/**
|
|
766
|
+
* Handle email verification for user
|
|
767
|
+
* @param auth
|
|
768
|
+
* @param actionCode
|
|
769
|
+
* @returns {error: string | null}
|
|
770
|
+
*/
|
|
771
|
+
this.verifyEmail = async (auth, actionCode) => {
|
|
772
|
+
try {
|
|
773
|
+
await applyActionCode(auth, actionCode);
|
|
774
|
+
return true;
|
|
775
|
+
} catch (error) {
|
|
776
|
+
throw new Error(`verifyEmail error: , ${error}`);
|
|
777
|
+
}
|
|
778
|
+
};
|
|
779
|
+
/**
|
|
780
|
+
* confirm that password reset link is correct
|
|
781
|
+
* @param auth
|
|
782
|
+
* @param actionCode
|
|
783
|
+
* @returns {email: string | null}
|
|
784
|
+
*/
|
|
785
|
+
this.verifyPasswordResetLink = async (auth, actionCode) => {
|
|
786
|
+
try {
|
|
787
|
+
return await verifyPasswordResetCode(auth, actionCode);
|
|
788
|
+
} catch (error) {
|
|
789
|
+
throw new Error(`passwordResetLink error: , ${error}`);
|
|
790
|
+
}
|
|
791
|
+
};
|
|
792
|
+
}
|
|
793
|
+
/**
|
|
794
|
+
* register user and send email verification
|
|
795
|
+
* delete user if registration is not successful
|
|
796
|
+
* @param param0
|
|
797
|
+
*/
|
|
798
|
+
async registerWithEmailAndPassword({
|
|
799
|
+
auth,
|
|
800
|
+
email,
|
|
801
|
+
password,
|
|
802
|
+
userData,
|
|
803
|
+
persist = "session"
|
|
804
|
+
}) {
|
|
805
|
+
try {
|
|
806
|
+
if (persist) {
|
|
807
|
+
await setPersistence(auth, persist === "local" ? browserLocalPersistence : browserSessionPersistence);
|
|
808
|
+
}
|
|
809
|
+
const credential = await createUserWithEmailAndPassword(auth, email, password);
|
|
810
|
+
this.user = credential.user;
|
|
811
|
+
if (userData) {
|
|
812
|
+
await this.save(userData, credential.user.uid);
|
|
813
|
+
}
|
|
814
|
+
return credential.user.uid;
|
|
815
|
+
} catch (error) {
|
|
816
|
+
if (this.user) {
|
|
817
|
+
this.deleteAccount(this.user);
|
|
818
|
+
}
|
|
819
|
+
throw new Error(`registerWithEmailAndPassword error: , ${error}`);
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
/**
|
|
823
|
+
* confirm user is valid and get their information
|
|
824
|
+
* from firestore users table if exists
|
|
825
|
+
* @param { string , string, boolean} credentials - login credentials
|
|
826
|
+
* @returns {Promise<QueryReturn>}
|
|
827
|
+
*/
|
|
828
|
+
async login({ email, password, auth, persist = "session", verifyEmail = false }) {
|
|
829
|
+
try {
|
|
830
|
+
if (persist) {
|
|
831
|
+
await setPersistence(auth, persist === "local" ? browserLocalPersistence : browserSessionPersistence);
|
|
832
|
+
}
|
|
833
|
+
const userAuth = await signInWithEmailAndPassword(auth, email, password);
|
|
834
|
+
if (userAuth) {
|
|
835
|
+
if (verifyEmail && !userAuth.user.emailVerified) {
|
|
836
|
+
return { message: "Email is not verified", status: "error" };
|
|
837
|
+
} else {
|
|
838
|
+
return { message: "User successfully logged in", status: "success", data: userAuth.user };
|
|
839
|
+
}
|
|
840
|
+
} else {
|
|
841
|
+
return { message: "Unknown account", status: "error" };
|
|
842
|
+
}
|
|
843
|
+
} catch (error) {
|
|
844
|
+
throw new Error(`login error: , ${error}`);
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
/**
|
|
848
|
+
* sign user with user's number
|
|
849
|
+
* @param param0
|
|
850
|
+
* @returns
|
|
851
|
+
*/
|
|
852
|
+
async signInWithPhoneNumber({ auth, phoneNumber, appVerifier }) {
|
|
853
|
+
try {
|
|
854
|
+
return await signInWithPhoneNumber(auth, phoneNumber, appVerifier);
|
|
855
|
+
} catch (error) {
|
|
856
|
+
throw new Error(`signInWithPhoneNumber error: , ${error}`);
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
/**
|
|
860
|
+
* Check if user is currently logged in
|
|
861
|
+
* @param auth
|
|
862
|
+
* @returns
|
|
863
|
+
*/
|
|
864
|
+
isLoggedIn(auth) {
|
|
865
|
+
try {
|
|
866
|
+
if (auth.currentUser) {
|
|
867
|
+
return true;
|
|
868
|
+
} else {
|
|
869
|
+
return false;
|
|
870
|
+
}
|
|
871
|
+
} catch (error) {
|
|
872
|
+
throw new Error(`isLoggedIn error: , ${error}`);
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
/**
|
|
876
|
+
* Reset logged in user's password
|
|
877
|
+
* @param {Auth, string} param0
|
|
878
|
+
* @returns {boolean}
|
|
879
|
+
*/
|
|
880
|
+
async resetPassword({ auth, newPassword }) {
|
|
881
|
+
try {
|
|
882
|
+
await updatePassword(auth.currentUser, newPassword);
|
|
883
|
+
return true;
|
|
884
|
+
} catch (e) {
|
|
885
|
+
throw new Error(`resetPassword error: , ${e}`);
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
/**
|
|
889
|
+
* send password reset message to user's email address
|
|
890
|
+
* @param param0
|
|
891
|
+
* @returns
|
|
892
|
+
*/
|
|
893
|
+
async sendPasswordResetMessage({ auth, email }) {
|
|
894
|
+
try {
|
|
895
|
+
await sendPasswordResetEmail(auth, email);
|
|
896
|
+
return true;
|
|
897
|
+
} catch (e) {
|
|
898
|
+
throw new Error(`sendPasswordResetMessage error: , ${e}`);
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
/**
|
|
902
|
+
* logout users and end current session
|
|
903
|
+
* @param param0
|
|
904
|
+
* @returns
|
|
905
|
+
*/
|
|
906
|
+
async logout({ auth }) {
|
|
907
|
+
try {
|
|
908
|
+
await signOut(auth);
|
|
909
|
+
return true;
|
|
910
|
+
} catch (e) {
|
|
911
|
+
throw new Error(`logout error: , ${e}`);
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
/**
|
|
915
|
+
* create and validate users
|
|
916
|
+
* @param param0
|
|
917
|
+
*/
|
|
918
|
+
async loginWithMultiAuthFactor({ email, password, auth, recaptcha, getNumber, persist = true, verifyEmail = false }) {
|
|
919
|
+
try {
|
|
920
|
+
if (persist) {
|
|
921
|
+
await setPersistence(auth, browserSessionPersistence);
|
|
922
|
+
}
|
|
923
|
+
const credential = await signInWithEmailAndPassword(auth, email, password);
|
|
924
|
+
if (credential) {
|
|
925
|
+
if (verifyEmail && !credential.user.emailVerified) {
|
|
926
|
+
await this.sendEmailVerification(credential.user);
|
|
927
|
+
throw new Error("Email is not verified");
|
|
928
|
+
}
|
|
929
|
+
let phoneNumber;
|
|
930
|
+
if (getNumber) {
|
|
931
|
+
phoneNumber = await this.getPhoneNumber(credential.user.uid);
|
|
932
|
+
}
|
|
933
|
+
const user = credential.user;
|
|
934
|
+
const verificationId = await this.setMultiFactorEnrollment(user, recaptcha, auth, phoneNumber != null ? phoneNumber : credential.user.phoneNumber);
|
|
935
|
+
if (verificationId) {
|
|
936
|
+
return { verificationId, user };
|
|
937
|
+
} else {
|
|
938
|
+
return null;
|
|
939
|
+
}
|
|
940
|
+
} else {
|
|
941
|
+
return null;
|
|
942
|
+
}
|
|
943
|
+
} catch (e) {
|
|
944
|
+
const error = e;
|
|
945
|
+
if (error.code === "auth/multi-factor-auth-required") {
|
|
946
|
+
const resolver = getMultiFactorResolver(auth, error);
|
|
947
|
+
return await this.sendOTP(resolver, recaptcha, auth);
|
|
948
|
+
} else {
|
|
949
|
+
throw new Error(`loginWithMultiAuthFactor error: ${error}`);
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
/**
|
|
954
|
+
* delete user account
|
|
955
|
+
* @param user
|
|
956
|
+
*/
|
|
957
|
+
async deleteAccount(user) {
|
|
958
|
+
try {
|
|
959
|
+
await Promise.all([
|
|
960
|
+
deleteUser(user),
|
|
961
|
+
this.delete(user.uid)
|
|
962
|
+
]);
|
|
963
|
+
} catch (error) {
|
|
964
|
+
throw new Error(`deleteAccount error: ,${error}`);
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
/**
|
|
968
|
+
* resend email verification for expired link
|
|
969
|
+
* @param auth
|
|
970
|
+
* @returns
|
|
971
|
+
*/
|
|
972
|
+
async sendEmailVerification(user) {
|
|
973
|
+
try {
|
|
974
|
+
if (user) {
|
|
975
|
+
sendEmailVerification(user);
|
|
976
|
+
return null;
|
|
977
|
+
} else {
|
|
978
|
+
return "Unknown user account, please sign up!";
|
|
979
|
+
}
|
|
980
|
+
} catch (error) {
|
|
981
|
+
throw new Error(`sendEmailVerification error: , ${error}`);
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
async doPasswordReset(auth, actionCode, newPassword) {
|
|
985
|
+
try {
|
|
986
|
+
await confirmPasswordReset(auth, actionCode, newPassword);
|
|
987
|
+
return true;
|
|
988
|
+
} catch (e) {
|
|
989
|
+
throw new Error(`doPasswordReset error: , ${e}`);
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
};
|
|
993
|
+
|
|
994
|
+
// src/Storage.ts
|
|
995
|
+
import {
|
|
996
|
+
ref,
|
|
997
|
+
uploadString,
|
|
998
|
+
getDownloadURL,
|
|
999
|
+
uploadBytesResumable,
|
|
1000
|
+
deleteObject
|
|
1001
|
+
} from "firebase/storage";
|
|
1002
|
+
|
|
1003
|
+
// src/constants.ts
|
|
1004
|
+
var fbsession = /^firebase:authUser:/;
|
|
1005
|
+
var UPLOADTYPES = /* @__PURE__ */ ((UPLOADTYPES2) => {
|
|
1006
|
+
UPLOADTYPES2["IMAGES"] = "images";
|
|
1007
|
+
UPLOADTYPES2["DOCUMENTS"] = "documents";
|
|
1008
|
+
UPLOADTYPES2["VIDEOS"] = "videos";
|
|
1009
|
+
UPLOADTYPES2["AUDIOS"] = "audios";
|
|
1010
|
+
return UPLOADTYPES2;
|
|
1011
|
+
})(UPLOADTYPES || {});
|
|
1012
|
+
var AUTH_PROVIDERS = /* @__PURE__ */ ((AUTH_PROVIDERS2) => {
|
|
1013
|
+
AUTH_PROVIDERS2[AUTH_PROVIDERS2["GOOGLE"] = 0] = "GOOGLE";
|
|
1014
|
+
AUTH_PROVIDERS2[AUTH_PROVIDERS2["APPLE"] = 1] = "APPLE";
|
|
1015
|
+
AUTH_PROVIDERS2[AUTH_PROVIDERS2["FACEBOOK"] = 2] = "FACEBOOK";
|
|
1016
|
+
AUTH_PROVIDERS2[AUTH_PROVIDERS2["TWITTER"] = 3] = "TWITTER";
|
|
1017
|
+
return AUTH_PROVIDERS2;
|
|
1018
|
+
})(AUTH_PROVIDERS || {});
|
|
1019
|
+
|
|
1020
|
+
// src/Storage.ts
|
|
1021
|
+
var StorageUpload = class {
|
|
1022
|
+
constructor(props) {
|
|
1023
|
+
// validate file (size, type)
|
|
1024
|
+
this.validateFile = (file, storageRef) => {
|
|
1025
|
+
var _a;
|
|
1026
|
+
let goodSize = false;
|
|
1027
|
+
let goodType = false;
|
|
1028
|
+
let extension = "";
|
|
1029
|
+
let goodTypes = [];
|
|
1030
|
+
if (typeof file !== "string") {
|
|
1031
|
+
if (storageRef === "images" /* IMAGES */) {
|
|
1032
|
+
goodTypes = ["image/png", "image/jpg", "image/jpeg"];
|
|
1033
|
+
extension = this.getExtensionName(file.type);
|
|
1034
|
+
} else if (storageRef === "documents" /* DOCUMENTS */) {
|
|
1035
|
+
goodTypes = [
|
|
1036
|
+
"application/pdf",
|
|
1037
|
+
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
1038
|
+
"application/vnd.ms-excel"
|
|
1039
|
+
];
|
|
1040
|
+
extension = this.getDocExtensionName(file.type);
|
|
1041
|
+
} else if (storageRef === "videos" /* VIDEOS */) {
|
|
1042
|
+
goodTypes = ["video/mp4", "video/m4v"];
|
|
1043
|
+
} else if (storageRef === "audios" /* AUDIOS */) {
|
|
1044
|
+
goodTypes = ["audio/mp3", "audio/mpeg"];
|
|
1045
|
+
}
|
|
1046
|
+
goodType = goodTypes.includes(file.type);
|
|
1047
|
+
goodSize = file.size > 0 && file.size <= this.maxSize;
|
|
1048
|
+
} else {
|
|
1049
|
+
goodType = true;
|
|
1050
|
+
goodSize = true;
|
|
1051
|
+
}
|
|
1052
|
+
if (!goodSize || !goodType) {
|
|
1053
|
+
this.setUploadError(storageRef, (_a = this.maxSize) != null ? _a : 1e6, goodSize);
|
|
1054
|
+
} else {
|
|
1055
|
+
this.setFilePath(storageRef, extension);
|
|
1056
|
+
}
|
|
1057
|
+
};
|
|
1058
|
+
/**
|
|
1059
|
+
* Get human readable form for file size
|
|
1060
|
+
* @param size
|
|
1061
|
+
* @returns
|
|
1062
|
+
*/
|
|
1063
|
+
this.sizeMetric = (size) => {
|
|
1064
|
+
let result = `${size} bytes`;
|
|
1065
|
+
if (size <= 999999) {
|
|
1066
|
+
result = `${Math.round(size / 1e3)} Kb`;
|
|
1067
|
+
} else if (size <= 999999999) {
|
|
1068
|
+
result = `${Math.round(size / 1e6)} Mb`;
|
|
1069
|
+
} else if (size <= 999999999999) {
|
|
1070
|
+
result = `${Math.round(size / 1e9)} Gb`;
|
|
1071
|
+
} else if (size <= 999999999999999) {
|
|
1072
|
+
result = `${Math.round(size / 1e12)} T`;
|
|
1073
|
+
}
|
|
1074
|
+
return result;
|
|
1075
|
+
};
|
|
1076
|
+
/**
|
|
1077
|
+
* Get file extensions
|
|
1078
|
+
* @param fileType
|
|
1079
|
+
* @returns
|
|
1080
|
+
*/
|
|
1081
|
+
this.getExtensionName = (fileType) => {
|
|
1082
|
+
let ext = "";
|
|
1083
|
+
if (fileType === "application/pdf") {
|
|
1084
|
+
ext = "pdf";
|
|
1085
|
+
}
|
|
1086
|
+
if (fileType === "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") {
|
|
1087
|
+
ext = ".xlsx";
|
|
1088
|
+
}
|
|
1089
|
+
if (fileType === "application/vnd.ms-excel") {
|
|
1090
|
+
ext = ".xls";
|
|
1091
|
+
}
|
|
1092
|
+
return ext;
|
|
1093
|
+
};
|
|
1094
|
+
this.getDocExtensionName = (fileType) => {
|
|
1095
|
+
let ext = "";
|
|
1096
|
+
if (fileType === "image/png") {
|
|
1097
|
+
ext = "png";
|
|
1098
|
+
}
|
|
1099
|
+
if (fileType === "image/jpg") {
|
|
1100
|
+
ext = "jpg";
|
|
1101
|
+
}
|
|
1102
|
+
if (fileType === "image/jpeg") {
|
|
1103
|
+
ext = "jpeg";
|
|
1104
|
+
}
|
|
1105
|
+
return ext;
|
|
1106
|
+
};
|
|
1107
|
+
/**
|
|
1108
|
+
* setting error messages for failed file validation
|
|
1109
|
+
* @param ref
|
|
1110
|
+
* @param maxSize
|
|
1111
|
+
* @param isGoodSize
|
|
1112
|
+
* @param isGoodType
|
|
1113
|
+
*/
|
|
1114
|
+
this.setUploadError = (ref2, maxSize, isGoodSize) => {
|
|
1115
|
+
this.uploadError = !isGoodSize ? `File size is must not be larger than ${this.sizeMetric(maxSize)}` : `File is not a valid ${ref2 === "images" /* IMAGES */ ? "image" : ref2 === "documents" /* DOCUMENTS */ ? "document" : ref2 === "videos" /* VIDEOS */ ? "video" : "audio"}`;
|
|
1116
|
+
};
|
|
1117
|
+
// generate new file name and extension
|
|
1118
|
+
this.setFilePath = (ref2, ext) => {
|
|
1119
|
+
const d = /* @__PURE__ */ new Date();
|
|
1120
|
+
const fileExtension = ref2 === "images" /* IMAGES */ || ref2 === "documents" /* DOCUMENTS */ ? "." + ext : (
|
|
1121
|
+
// (ref===UPLOADTYPES.DOCUMENTS?'.pdf':(
|
|
1122
|
+
ref2 === "videos" /* VIDEOS */ ? ".mp4" : ".mp3"
|
|
1123
|
+
);
|
|
1124
|
+
const fileName = generateRandomString(30);
|
|
1125
|
+
this.fullPath = ref2.concat(
|
|
1126
|
+
`/`,
|
|
1127
|
+
`${this.additionalPath}/`,
|
|
1128
|
+
`${fileName}_${d.getTime()}${fileExtension}`
|
|
1129
|
+
);
|
|
1130
|
+
};
|
|
1131
|
+
/**
|
|
1132
|
+
* upload base64 data_url
|
|
1133
|
+
* @param reference
|
|
1134
|
+
*/
|
|
1135
|
+
this.uploadAsString = async (reference) => {
|
|
1136
|
+
try {
|
|
1137
|
+
const result = await uploadString(reference, this.file, "data_url");
|
|
1138
|
+
if (result) {
|
|
1139
|
+
return await getDownloadURL(ref(this.storage, result.ref.fullPath));
|
|
1140
|
+
} else {
|
|
1141
|
+
return false;
|
|
1142
|
+
}
|
|
1143
|
+
} catch (error) {
|
|
1144
|
+
throw new Error(`uploadAsString: , ${error}`);
|
|
1145
|
+
}
|
|
1146
|
+
};
|
|
1147
|
+
/**
|
|
1148
|
+
* upload blobs and files
|
|
1149
|
+
* @param reference
|
|
1150
|
+
*/
|
|
1151
|
+
this.uploadAsFile = async (reference) => {
|
|
1152
|
+
try {
|
|
1153
|
+
const snapShot = await uploadBytesResumable(reference, this.file);
|
|
1154
|
+
if (snapShot) {
|
|
1155
|
+
return await getDownloadURL(ref(this.storage, this.fullPath));
|
|
1156
|
+
} else {
|
|
1157
|
+
return false;
|
|
1158
|
+
}
|
|
1159
|
+
} catch (error) {
|
|
1160
|
+
throw new Error(`uploadAsFile: , ${error}`);
|
|
1161
|
+
}
|
|
1162
|
+
};
|
|
1163
|
+
const { file, basePath, otherPath, maxSize, storage } = props;
|
|
1164
|
+
this.storage = storage;
|
|
1165
|
+
if (file) {
|
|
1166
|
+
this.additionalPath = otherPath;
|
|
1167
|
+
this.maxSize = maxSize != null ? maxSize : 1e6;
|
|
1168
|
+
this.file = file;
|
|
1169
|
+
this.validateFile(file, basePath);
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
/**
|
|
1173
|
+
* Upload images, documents and videos
|
|
1174
|
+
* @param progressMonitor
|
|
1175
|
+
*/
|
|
1176
|
+
async doUpload() {
|
|
1177
|
+
if (this.uploadError) {
|
|
1178
|
+
throw new Error(`doUPload Error: ,${this.uploadError}`);
|
|
1179
|
+
} else {
|
|
1180
|
+
try {
|
|
1181
|
+
const reference = ref(this.storage, this.fullPath);
|
|
1182
|
+
if (typeof this.file === "string") {
|
|
1183
|
+
console.log("uploading as string");
|
|
1184
|
+
return await this.uploadAsString(reference);
|
|
1185
|
+
} else {
|
|
1186
|
+
console.log("uploading as file");
|
|
1187
|
+
return await this.uploadAsFile(reference);
|
|
1188
|
+
}
|
|
1189
|
+
} catch (error) {
|
|
1190
|
+
throw new Error(`doUPload Error: ,${error}`);
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
/**
|
|
1195
|
+
* delete files from storage
|
|
1196
|
+
* @param filePath
|
|
1197
|
+
* @param storage
|
|
1198
|
+
* @returns {boolean}
|
|
1199
|
+
*/
|
|
1200
|
+
static async deleteFile(filePath, storage) {
|
|
1201
|
+
try {
|
|
1202
|
+
const delRef = ref(storage, filePath);
|
|
1203
|
+
await deleteObject(delRef);
|
|
1204
|
+
return true;
|
|
1205
|
+
} catch (error) {
|
|
1206
|
+
return false;
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
};
|
|
1210
|
+
|
|
1211
|
+
// src/useAuth.ts
|
|
1212
|
+
import { useEffect } from "react";
|
|
1213
|
+
import { onAuthStateChanged } from "firebase/auth";
|
|
1214
|
+
function useAuth(auth, callback) {
|
|
1215
|
+
useEffect(() => {
|
|
1216
|
+
const unsubscribe = onAuthStateChanged(auth, async (authUser) => {
|
|
1217
|
+
callback(authUser);
|
|
1218
|
+
});
|
|
1219
|
+
return () => unsubscribe();
|
|
1220
|
+
}, []);
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
// src/useCounter.ts
|
|
1224
|
+
import { useCallback, useEffect as useEffect2, useState } from "react";
|
|
1225
|
+
var useCount = (param) => {
|
|
1226
|
+
const [data, setData] = useState(0);
|
|
1227
|
+
const [loading, setLoading] = useState(true);
|
|
1228
|
+
const fetcher = useCallback(async () => {
|
|
1229
|
+
if (!param.where)
|
|
1230
|
+
return;
|
|
1231
|
+
try {
|
|
1232
|
+
const model = param.model;
|
|
1233
|
+
const found = await model.countData(param.where);
|
|
1234
|
+
setData(found);
|
|
1235
|
+
} catch (error) {
|
|
1236
|
+
errorLogger("useCount: ", error);
|
|
1237
|
+
} finally {
|
|
1238
|
+
setLoading(false);
|
|
1239
|
+
}
|
|
1240
|
+
}, [
|
|
1241
|
+
JSON.stringify(param.where),
|
|
1242
|
+
loading
|
|
1243
|
+
]);
|
|
1244
|
+
useEffect2(() => {
|
|
1245
|
+
fetcher();
|
|
1246
|
+
}, [fetcher]);
|
|
1247
|
+
return [data, loading, setLoading];
|
|
1248
|
+
};
|
|
1249
|
+
|
|
1250
|
+
// src/useFetch.ts
|
|
1251
|
+
import { useCallback as useCallback2, useEffect as useEffect3, useState as useState2 } from "react";
|
|
1252
|
+
var useFetch = (param) => {
|
|
1253
|
+
const [data, setData] = useState2();
|
|
1254
|
+
const [loading, setLoading] = useState2(true);
|
|
1255
|
+
const fetcher = useCallback2(async () => {
|
|
1256
|
+
var _a, _b, _c, _d;
|
|
1257
|
+
if (!param.reference && !param.where)
|
|
1258
|
+
return;
|
|
1259
|
+
try {
|
|
1260
|
+
const model = param.model;
|
|
1261
|
+
if (param.reference) {
|
|
1262
|
+
const found = await model.find(param.reference);
|
|
1263
|
+
if (found)
|
|
1264
|
+
setData(model.data);
|
|
1265
|
+
} else if (param.where) {
|
|
1266
|
+
const found = await model.findWhere({
|
|
1267
|
+
wh: param.where,
|
|
1268
|
+
lim: (_b = (_a = param.filter) == null ? void 0 : _a.limit) != null ? _b : 100,
|
|
1269
|
+
order: (_c = param.filter) == null ? void 0 : _c.orderBy,
|
|
1270
|
+
offset: (_d = param.filter) == null ? void 0 : _d.offset
|
|
1271
|
+
});
|
|
1272
|
+
setData(found);
|
|
1273
|
+
}
|
|
1274
|
+
} catch (error) {
|
|
1275
|
+
errorLogger("useFetch: ", error);
|
|
1276
|
+
} finally {
|
|
1277
|
+
setLoading(false);
|
|
1278
|
+
}
|
|
1279
|
+
}, [
|
|
1280
|
+
param.reference,
|
|
1281
|
+
JSON.stringify(param.where),
|
|
1282
|
+
JSON.stringify(param.filter),
|
|
1283
|
+
loading
|
|
1284
|
+
]);
|
|
1285
|
+
useEffect3(() => {
|
|
1286
|
+
fetcher();
|
|
1287
|
+
}, [fetcher]);
|
|
1288
|
+
return [data, loading, setLoading];
|
|
1289
|
+
};
|
|
1290
|
+
|
|
1291
|
+
// src/useStream.ts
|
|
1292
|
+
import { useEffect as useEffect4 } from "react";
|
|
1293
|
+
var useStream = (param, callback) => {
|
|
1294
|
+
useEffect4(() => {
|
|
1295
|
+
let unsubscribe;
|
|
1296
|
+
try {
|
|
1297
|
+
const { model, reference, where: where2, filter } = param;
|
|
1298
|
+
if (reference) {
|
|
1299
|
+
unsubscribe = model.stream((data) => {
|
|
1300
|
+
if (data)
|
|
1301
|
+
callback(data);
|
|
1302
|
+
}, reference);
|
|
1303
|
+
} else if (where2) {
|
|
1304
|
+
const allInvalid = where2.every((item) => item.value === void 0);
|
|
1305
|
+
if (allInvalid) {
|
|
1306
|
+
callback([]);
|
|
1307
|
+
return;
|
|
1308
|
+
}
|
|
1309
|
+
unsubscribe = model.streamWhere(
|
|
1310
|
+
where2,
|
|
1311
|
+
(data) => {
|
|
1312
|
+
if (data)
|
|
1313
|
+
callback(data);
|
|
1314
|
+
},
|
|
1315
|
+
100,
|
|
1316
|
+
filter == null ? void 0 : filter.orderBy,
|
|
1317
|
+
filter == null ? void 0 : filter.offset
|
|
1318
|
+
);
|
|
1319
|
+
}
|
|
1320
|
+
} catch (error) {
|
|
1321
|
+
errorLogger("useStream error:", error);
|
|
1322
|
+
}
|
|
1323
|
+
return () => {
|
|
1324
|
+
if (typeof unsubscribe === "function") {
|
|
1325
|
+
unsubscribe();
|
|
1326
|
+
}
|
|
1327
|
+
};
|
|
1328
|
+
}, [JSON.stringify(param.where), JSON.stringify(param.filter), param.reference]);
|
|
1329
|
+
};
|
|
1330
|
+
export {
|
|
1331
|
+
AUTH_PROVIDERS,
|
|
1332
|
+
BaseModel,
|
|
1333
|
+
StorageUpload,
|
|
1334
|
+
UPLOADTYPES,
|
|
1335
|
+
Users,
|
|
1336
|
+
camelCaseToNormal,
|
|
1337
|
+
camelToTitleCase,
|
|
1338
|
+
convertUnicode,
|
|
1339
|
+
errorLogger,
|
|
1340
|
+
fbsession,
|
|
1341
|
+
fetchCurrentTimestamp,
|
|
1342
|
+
formHasError,
|
|
1343
|
+
generateRandomString,
|
|
1344
|
+
getDaysAgo,
|
|
1345
|
+
getInitials,
|
|
1346
|
+
getNthNumberOfArray,
|
|
1347
|
+
isValidEmail,
|
|
1348
|
+
isValidPassword,
|
|
1349
|
+
moneyFormatter,
|
|
1350
|
+
nairaFormatter,
|
|
1351
|
+
noEmptyField,
|
|
1352
|
+
numberToAlphabet,
|
|
1353
|
+
range,
|
|
1354
|
+
timeAgo,
|
|
1355
|
+
useAuth,
|
|
1356
|
+
useCount,
|
|
1357
|
+
useFetch,
|
|
1358
|
+
useStream
|
|
1359
|
+
};
|