method/use.js

/* eslint-disable promise/always-return */
/**
 * @module method/use
 */

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

const {has} = require('lodash');

const db = admin.firestore();

/**
 * Consume an invite code
 *
 * @param  {string} uid - Referee’s Firebase User ID, e.g. hk_wx5555556.
 * @param  {string} inviteCode - The invite code
 *
 * @return {object} An object containing data related to the invite code.
 */
const use = (uid, inviteCode) => {
  return db.runTransaction((transaction) => {
    // 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;

      // Assign userMeta and inviteCodeData to null
      let userMeta = null;
      let inviteCodeData = null;

      // Check if the userMeta exists
      if (userMetaDoc.exists) {
        userMeta = userMetaDoc.data();
      } else {
        throw new functions.https.HttpsError(
          'not-found',
          'Error while attempting to use invite code: '
              + 'Cannot find specified user in user meta'
        );
      }

      // Check if user has already consumed an invite code previosuly
      if (has(userMeta, 'inviteCode')) {
        throw new functions.https.HttpsError(
          'cancelled',
          'Error while attempting to use invite code: '
              + 'User has previously consumed an invite code'
        );
      }

      // Check if the invite code exists
      if (inviteCodeDoc.exists) {
        inviteCodeData = inviteCodeDoc.data();
      } else {
        throw new functions.https.HttpsError(
          'not-found',
          'Error while attempting to use invite code: '
              + 'Cannot find specified invite code'
        );
      }

      // Assign expiration and current timestamps
      const expiration = inviteCodeData['expiration'].toMillis();
      const now = Date.now();

      // Check if invite code is not expired
      if (now < expiration) {
        // Check if invite code still has quota
        if (inviteCodeData['quota'] > 0) {
          // Save invite code data to userMeta
          transaction.update(userMetaRef, {
            inviteCode: {
              code: inviteCode,
              description: inviteCodeData['description'],
            },
          });

          // Decrease quota count of invite code
          transaction.update(inviteCodeRef, {
            quota: FieldValue.increment(-1),
          });
        } else {
          throw new functions.https.HttpsError(
            'resource-exhausted',
            'Error while attempting to use invite code: '
                + 'Invite code has been used too many times'
          );
        }
      } else {
        throw new functions.https.HttpsError(
          'cancelled',
          'Error while attempting to use invite code: '
              + 'Invite code has already expired'
        );
      }
    }).then(() => {
      return true;
    }).catch((error) => {
      // throw new Error('Error while attempting to use invite code: ' + error);
      throw error;
    });
  });
};

module.exports = use;