import { User } from '@tutorbook/model'; import to from 'await-to-js'; import { db, auth, UserRecord, FirebaseError, DocumentSnapshot, DocumentReference, } from './firebase'; /** * Creates a new user and handles various errors: * - The `auth/email-already-exists` error by updating the existing error (see * the above helper function for how that's implemented). * - The `auth/phone-number-already-exists` error by creating the user w/out the * given phone number (this is a bit hacky and we might want to re-think it * later on). * * While this function doesn't actually return anything, it does perform side * effects on the given `user` object (i.e. adding the uID). */ export default async function createUser( user: User, parents?: User[] ): Promise { /* eslint-disable no-param-reassign, no-shadow */ console.log(`[DEBUG] Creating user ${user.toString()}...`); const [err, userRecord] = await to( auth.createUser({ disabled: false, displayName: user.name, photoURL: user.photo ? user.photo : undefined, email: user.email, emailVerified: false, phoneNumber: user.phone ? user.phone : undefined, }) ); if (err && err.code === 'auth/email-already-exists') { console.log('[DEBUG] Handling email address already exists error...'); const [err, userRecord] = await to( auth.getUserByEmail(user.email) ); if (err) throw new Error( `${err.name} fetching ${user.toString()}: ${err.message}` ); user.id = (userRecord as UserRecord).uid; console.log(`[DEBUG] Fetched ${user.name}'s account (${user.id}).`); } else if (err && err.code === 'auth/phone-number-already-exists') { console.log('[DEBUG] Handling phone number already exists error...'); const [err, userRecord] = await to( auth.createUser({ disabled: false, displayName: user.name, photoURL: user.photo ? user.photo : undefined, email: user.email, emailVerified: false, }) ); if (err) throw new Error( `${err.name} creating ${user.toString()}: ${err.message}` ); user.id = (userRecord as UserRecord).uid; console.log(`[DEBUG] Created ${user.name}'s account (${user.id}).`); } else if (err) { throw new Error(`${err.name} creating ${user.toString()}: ${err.message}`); } else { user.id = (userRecord as UserRecord).uid; console.log(`[DEBUG] Created ${user.name}'s account (${user.id}).`); } const userRef: DocumentReference = db.collection('users').doc(user.id); const userDoc: DocumentSnapshot = await userRef.get(); if (userDoc.exists) throw new Error( `Profile (${user.id}) already exists. Call the '/api/users/${user.id}' ` + `endpoint instead.` ); if (parents) await Promise.all( parents.map(async (parent: User) => { console.log(`[DEBUG] Creating parent ${parent.toString()}...`); const [err] = await to(createUser(parent)); if (parent.id) { user.parents.push(parent.id); } else if (err) { throw new Error(`${err.name} creating parent: ${err.message}`); } else { throw new Error(`Parent (${parent.toString()}) did not have ID.`); } console.log(`[DEBUG] Created parent ${parent.toString()}.`); }) ); console.log('[DEBUG] Setting profile...'); await userRef.set(user.toFirestore()); console.log(`[DEBUG] Set ${user.name}'s profile (${user.id}).`); /* eslint-enable no-param-reassign, no-shadow */ }