method/create.js

/**
 * @module method/create
 */

const functions = require('firebase-functions');
const admin = require('firebase-admin');
const Timestamp = admin.firestore.Timestamp;

const {has} = require('lodash');
const generate = require('nanoid/generate');

const db = admin.firestore();

/**
 * Create a new one-time invite code
 *
 * @param  {string} uid - Referrer’s Firebase User ID, e.g. hk_wx5555556.
 *
 * @return {object} Data object of Generated one-time invite code.
 */
const create = (uid) => {
  /**
   * Assign set of character used for invite code
   * @see https://www.notion.so/goodfinancial/App-Invite-Code-23cfec110c434c6f93421dbab36a5eb8
   */
  const charSet = 'EFGHJKLMNQRSUVWXY';

  // Trial count of invite code registration
  let trialCount = 5;

  // Register invite code to Cloud Firestore
  const codeRegistration = db.runTransaction((transaction) => {
    // Generate invite code
    const inviteCode = generate(charSet, 6);

    // Assign references to Cloud Firestore
    const userMetaRef = db.collection('appUserMeta').doc(uid);
    const inviteCodeRef = db.collection('appGlobalSettings')
        .doc('inviteCode')
        .collection('codes')
        .doc(inviteCode);

    return transaction.getAll(userMetaRef, inviteCodeRef).then((docs) => {
      // Reassign docs
      const [userMetaDoc, inviteCodeDoc] = docs;

      // Check if generated invite code exists
      if (inviteCodeDoc.exists) {
        return Promise.reject('Generated invite code already exists');
      }

      // Check if the userMeta exists
      if (userMetaDoc.exists) {
        // Get document data
        const userMeta = userMetaDoc.data();

        // Assign new count of invite code generated
        const inviteCodeGeneratedVal = has(userMeta, 'inviteCodeGenerated')
            ? userMeta['inviteCodeGenerated'] + 1
            : 1

        // Update code generation count
        transaction.update(userMetaRef, {
          inviteCodeGenerated: inviteCodeGeneratedVal,
        });

        // Expiration time
        const expiredInHours = 168;
        const expiredInMill = expiredInHours * 3600 * 1000;
        const expirationTimestamp = Date.now() + expiredInMill;

        // Save invite code to Cloud Firestore
        transaction.set(inviteCodeRef, {
          description: uid,
          expiration: Timestamp.fromMillis(expirationTimestamp),
          quota: 1,
        });

        return Promise.resolve({
          inviteCode: inviteCode,
          description: uid,
          expiration: expirationTimestamp,
          quota: 1,
        });
      } else {
        return Promise.reject('Cannot find specified user in user meta');
      }
    })
  });

  // Run invite code registration
  const runCodeRegistration = () => {
    return new Promise((resolve, reject) => {
      // If trial count is more than 0
      if (trialCount > 0) {
        // Consume one trial count
        trialCount--;

        // Run codeRegistration
        return codeRegistration.then((registeredCode) => {
          return resolve(registeredCode);
        }).catch((error) => {

          // If trial count is more than 0
          if (trialCount > 0) {
            // Log error
            console.error('Error while generating invite code: '
                + error
                + '. Retrying registration…'
            );

            // Rerun runCodeRegistration
            return runCodeRegistration().then(resolve).catch(reject);
          } else {
            // Log error
            console.error('Error while generating invite code: ' + error);

            // Abort function
            return reject('Cancelled invite code generation: Reached retry limit');
          }
        });
      }
    });
  };

  // Initial run of runCodeRegistration
  return runCodeRegistration().then((inviteCode) => {
    return inviteCode
  }).catch((error) => {
    throw new functions.https.HttpsError('cancelled', error);
  });
};

module.exports = create;