import {addDoc, collection, CollectionReference, deleteDoc, doc, DocumentData, DocumentReference, FieldValue, getCountFromServer, getDoc, getDocs, onSnapshot, Query, query, QueryConstraint, QueryDocumentSnapshot, setDoc, updateDoc, where} from "firebase/firestore"; import {SetStateAction} from "react"; import {db} from "./firebaseConfig"; import {pathToArr} from "./util"; import {Products} from "../typeDefinitions"; import { AppDispatch } from "../config"; export default class FirebaseQuery { private createQuery(queryType:"collection"|"doc", path:string[]|string, constraints?:QueryConstraint[]|QueryConstraint) { switch (queryType) { case "collection": return query(collection(db, ...(pathToArr(path) as [any])), ...[...(constraints ? Array.isArray(constraints) ? constraints : [constraints] : [])]); case "doc": return doc(db, ...(pathToArr(path) as [any])); } } async getCount(path:string[]|string, constraints?:QueryConstraint[]|QueryConstraint):Promise { console.log("GET COUNT", path, constraints); const countObject = await getCountFromServer(this.createQuery("collection", path, constraints) as Query).catch((e) => console.log(e)); if (!countObject) return 0; return (countObject).data().count; } async add(path:string[]|string, data:{[key:string]:unknown}) { if (path.length % 2 === 0) { throw new Error("Provide a collection reference"); } return await addDoc(this.createQuery("collection", path) as CollectionReference, data); } async set(path:string[]|string, data:{[key:string]:unknown}, merge=false) { if (path.length % 2 === 1) { throw new Error("Provide a document reference"); } return await setDoc(this.createQuery("doc", path) as DocumentReference, data, {merge: merge}); } async update(path:string[]|string, data:{ [key: string]: unknown}) { if (path.length % 2 === 1) { throw new Error("Provide a document reference"); } console.log("update", path, data); return await updateDoc(this.createQuery("doc", path) as DocumentReference, data as { [key: string]: FieldValue | Partial | undefined}); } async delete(path:string[]|string) { if (path.length % 2 === 1) { throw new Error("Provide a document reference"); } await deleteDoc(this.createQuery("doc", path) as DocumentReference); } async copy(path: string[]|string, id: string, oId: string, product: Products, additionalData?: {[key: string]: unknown}) { const existing = await this.getDocData([...path, id]); console.log("existing", existing); existing.name = await this.getUniqueDocName(path, existing.name, oId, product); if (existing.default) { existing.default = false; } return await this.add(path, {...existing, ...additionalData}); } async getUniqueDocName(path: string[]|string, name: string, oId: string, product: Products): Promise { const q = this.createQuery("collection", path, [where("name", "==", name), where("oId", "==", oId), where("product", "==", product)]); const querySnapshot = await getDocs(q as Query); if (!querySnapshot.empty) { return this.getUniqueDocName(path, name += "-", oId, product); } return name; } async getDocData(path:string[]|string) { console.log("GET DOC", path); if (path.length % 2 === 1) { throw new Error("Provide a document reference"); } const result = await getDoc(this.createQuery("doc", path) as DocumentReference).catch((error) => { console.log(`Error in snapshot ${path}`, error); throw error; }); if (!result.exists()) { throw new Error(`Document doesn't exist. ${typeof path === "string" ? path : path.join(", ")}`); } const resultData = result.data() || {}; resultData.id = result.id; return resultData; } async getDocsWhere(path: string[]|string, constraints?:QueryConstraint[]|QueryConstraint, rawDocs=false) { const q = this.createQuery("collection", path, constraints); const docs = await getDocs(q as Query).catch((e) => console.log(e, q)); if (rawDocs) return docs; if (!docs) return {}; console.log("GET DOCS WHERE", docs.size, path, constraints); return docs.docs.reduce((acc: {[k: string]: any}, document) => { const docData = {...document.data(), id: document.id}; acc[document.id] = docData; return acc; }, {}); } async collectionUpdate(data:{[key:string]:unknown}, path: string[]|string, constraints?:QueryConstraint[]|QueryConstraint) { if (path.length % 2 === 0) { throw new Error("Provide a collection reference"); } const q = this.createQuery("collection", path, constraints) as Query; (await getDocs(q)).docs.forEach((doc) => { this.update(doc.ref.path.split("/"), data); }); } documentSnapshot(path: string[]|string, setState: SetStateAction) { console.log("Building snapshot query: ", path); const q = this.createQuery("doc", path) as DocumentReference; console.log("Query created"); return onSnapshot(q, (doc) => { console.log("Data", doc.data()); setState({...doc.data(), id: doc.id}); }, (error) => { console.log(`Error in snapshot ${path}`, error); }); } collectionSnapshot(setState:SetStateAction|((e: DocumentData|{[key: string]: any}) => void)|Promise<((e: DocumentData|{[key: string]: any}) => void)>, path: string[]|string, constraints?:QueryConstraint[]|QueryConstraint, dispatch?: AppDispatch, raw?: boolean) { const q = this.createQuery("collection", path, constraints) as Query; return onSnapshot(q, (querySnapshot: DocumentData) => { if (raw) { setState(querySnapshot); return; } const itemList: {[key: string]: any} = {}; querySnapshot.docs.forEach((doc: QueryDocumentSnapshot) => { const item = doc.data(); item.id = doc.id; itemList[doc.id] = item; }); dispatch ? dispatch(setState(itemList)) : setState(itemList); }, (error) => { console.log(`Error in snapshot ${path}`, error); console.log(constraints) }); } }