import * as admin from 'firebase-admin'; import firebase from '@tutorbook/firebase'; import { AccountInterface, Account } from './account'; /** * Type aliases so that we don't have to type out the whole type. We could try * importing these directly from the `@firebase/firestore-types` or the * `@google-cloud/firestore` packages, but that's not recommended. * @todo Perhaps figure out a way to **only** import the type defs we need. */ type DocumentData = firebase.firestore.DocumentData; type SnapshotOptions = firebase.firestore.SnapshotOptions; type DocumentSnapshot = firebase.firestore.DocumentSnapshot; type AdminDocumentSnapshot = admin.firestore.DocumentSnapshot; /** * Duplicate definition from the `@tutorbook/react-intercom` package. These are * all the valid datatypes for custom Intercom user attributes. * @see {@link https://www.intercom.com/help/en/articles/179-send-custom-user-attributes-to-intercom} */ type IntercomCustomAttribute = string | boolean | number | Date; /** * An `Org` object represents a non-profit organization that is using Tutorbook * to manage their virtual tutoring programs. * @typedef {Object} Org * @property members - An array of user UIDs that are members of this org. */ export interface OrgInterface extends AccountInterface { members: string[]; } export type OrgJSON = OrgInterface; export function isOrgJSON(json: any): json is OrgJSON { return (json as OrgJSON).members !== undefined; } export class Org extends Account implements OrgInterface { public members: string[] = []; public constructor(org: Partial = {}) { super(org); Object.entries(org).forEach(([key, val]: [string, any]) => { if (val && key in this && !(key in new Account())) /* eslint-disable-next-line @typescript-eslint/no-unsafe-assignment */ (this as Record)[key] = val; }); } /** * Converts this `Org` object into a `Record` that Intercom * can understand. * @see {@link https://developers.intercom.com/installing-intercom/docs/javascript-api-attributes-objects#section-data-attributes} */ public toIntercom(): Record { const { id, photo, ref, ...rest } = this; const isFilled: (val: any) => boolean = (val: any) => { switch (typeof val) { case 'string': return val !== ''; case 'boolean': return true; case 'number': return true; case 'undefined': return false; case 'object': return Object.values(val).filter(isFilled).length > 0; default: return !!val; } }; const isValid: (val: any) => boolean = (val: any) => { if (typeof val === 'string') return true; if (typeof val === 'boolean') return true; if (typeof val === 'number') return true; if (val instanceof Date) return true; return false; }; const intercomValues: Record = Object.fromEntries( Object.entries(rest) .filter(([key, val]) => isFilled(val)) .map(([key, val]) => [key, isValid(val) ? val : JSON.stringify(val)]) ); return { ...intercomValues, ...super.toIntercom() }; } public static fromFirestore( snapshot: DocumentSnapshot | AdminDocumentSnapshot, options?: SnapshotOptions ): Org { const orgData: DocumentData | undefined = snapshot.data(options); if (orgData) { return new Org({ ...orgData, ref: snapshot.ref, id: snapshot.id, }); } console.warn( `[WARNING] Tried to create account (${snapshot.ref.id}) from ` + 'non-existent Firestore document.' ); return new Org(); } public static fromJSON(json: OrgJSON): Org { return new Org(json); } public toJSON(): OrgJSON { const { ref, ...rest } = this; return { ...rest }; } }