Source: ZEventsApiUser/ZEventsApiUser.js

import ifPromise from 'onyx-common/ifPromise'
import hasKey from 'onyx-common/hasKey'

const ZEventsApiUser = ({ prototype }) => {
  prototype.normalizeUser = function (_data) {
    const data = this.normalizeData(_data)
    data.email_visibility = !!data.email_visibility
    if (!hasKey(data, 'user')) data.user = {}
    if (!hasKey(data.user, 'privacy_setting')) {
      data.user.privacy_setting = {
        show_followees: true,
        show_attending: true,
        show_attended: true,
        show_in_user_search: true,
        show_in_speaker_search: true
      }
    }

    const mapProps = {
      user_id: ['id', val => parseInt(val)],
      avatar_url: 'avatarUrl',
      banner_url: 'bannerUrl',
      email: 'email',
      unverified_email: 'unverifiedEmail',
      is_email_verified: 'isEmailVerified',
      phone: 'phone',
      email_visibility: ['showEmail', val => !!val],
      pinterest: 'pinterest',
      instagram: 'instagram',
      facebook: 'facebook',
      twitter: 'twitter',
      shop_link: 'shopUrl',
      tagline: 'tagLine',
      firstname: 'firstName',
      lastname: 'lastName',
      has_avatar_image: ['hasAvatarImage', val => !!val],
      has_banner_image: ['hasBannerImage', val => !!val],
      share_url: 'shareUrl',
      is_current_user: ['isCurrentUser', val => !!val],
      username: 'username',
      is_followed: ['isFollowed', val => !!val],
      follower_count: ['followerCount', val => parseInt(val) || 0]
    }

    const ret = this.filterAndMapProps(data, mapProps)

    ret.isFollowed = ret.isFollowed || false
    ret.followerCount = ret.followerCount || 0

    // privacy settings
    ret.showFollowees = !!data.user.privacy_setting?.show_followees
    ret.showEventsAttending = !!data.user.privacy_setting?.show_events_attending
    ret.showEventsAttended = !!data.user.privacy_setting?.show_events_attended
    ret.showInUserSearch = !!data.user.privacy_setting?.show_in_user_search
    ret.showInSpeakerSearch = !!data.user.privacy_setting?.show_in_speaker_search

    if (!hasKey(ret, 'isCurrentUser')) ret.isCurrentUser = false

    return ret
  }

  prototype.normalizeUserProfile = prototype.normalizeUser

  prototype._createUser = function (_data) {
    const payload = {
      url: this.getUrl('/api/v1/users'),
      method: 'POST',
      requestType: 'json',
      data: {
        ..._data,
        ...this.getAuthParams()
      }
    }

    return this.fetchWrap(payload)
  }

  /**
   * @typedef {object} CreateUserReturn
   * @property {string} accessToken - access token
   * @property {string} refreshToken - refresh token
   * @property {number} expiresIn - seconds until accessToken is expired
   * @property {number} createdAt - when account was created
   */

  /**
   * @function createUser
   * @param {object} payload - The user payload
   * @param {string} payload.firstName - first name of user
   * @param {string} payload.lastName - last name of user
   * @param {string} payload.email - email address of user
   * @param {string} payload.password - password for user
   * @param {boolean} [payload.withLogin=true] - should we also log the new user in?
   * @returns {CreateUserReturn} - loggedIn user
   * @example
   *
   * createUser({
   *   firstName: 'Joe',
   *   lastName: 'Blow',
   *   email: 'joe@foo.com',
   *   password: '1234'
   * })
   */
  prototype.createUser = function (payload) {
    const finalPayload = {
      firstname: payload.firstName,
      lastname: payload.lastName,
      email: payload.email,
      password: payload.password
    }

    if (!hasKey(payload, 'withLogin')) payload.withLogin = true
    const withLogin = Boolean(payload.withLogin)

    const raw = ifPromise(payload, () => this._createUser(finalPayload))
    return raw
      .then(res => this.normalizeLoginableReturn(res.data))
      .then(data => {
        if (withLogin) return this.manualLogin(data)
        return data
      })
      .catch(res => {
        const mapError = {
          'Email Adress has already been taken': 'CREATE_USER_EMAIL_ADDRESS_TAKEN',
          'Password is too short (minimum is 6 characters)': 'CREATE_USER_PASSWORD_TOO_SHORT'
        }
        return this.onError('createUser', res, mapError)
      })
  }

  prototype._getUserAccount = function ({ id }) {
    const payload = {
      url: this.getUrl(`/api/v1/users/${id}/account/show`),
      method: 'GET'
    }

    return this.authenticatedFetchWrap(payload)
  }

  /**
   * @typedef {object} getUserAccountReturn
   * @property {number} id - user id
   * @property {string} firstName - first name
   * @property {string} lastName - last name
   * @property {string} tagLine - tag line for use in profile
   * @property {string} facebook - facebook handle
   * @property {string} instagram - instagram handle
   * @property {string} pinterest - pinterest handle
   * @property {string} linkedin - linkedin handle
   * @property {string} twitter - twitter handle
   * @property {string} avatarUrl - image url for avatar
   * @property {string} bannerUrl - image url for banner
   * @property {string} shopUrl - url for shop link
   * @property {string} email - email address
   * @property {string} phone - phone number
   * @property {boolean} hasAvatarImage - did the user upload an avatar image?
   * @property {boolean} hasBannerImage - did the user upload a banner image?
   * @property {boolean} showEmail - should email display on user's profile?  If this is false, then `email` will also be empty (unless requester is self or admin)
   * @property {boolean} showFollowees - show followees?
   * @property {boolean} showEventsAttended - show events attended?
   * @property {boolean} showEventsAttending - show events user is attending?
   * @property {boolean} showInUserSearch - show profile in user search?
   * @property {boolean} showInSpeakerSearch - show profile in speaker search?
   * @property {string} shareUrl - url to share profile
   */

  /**
   * @function getUserAccount
   * @param {object} payload - The user payload
   * @param {string} [payload.id='me'] - ID of user
   * @returns {getUserAccountReturn}
   * @example
   * getUserAccount()
   */

  prototype.getUserAccount = function (payload = {}) {
    const {
      id
    } = payload

    const finalPayload = {
      id: this.normalizeUserId(id)
    }

    const raw = ifPromise(payload, () => this._getUserAccount(finalPayload))
    return raw
      .then(res => this.normalizeUser(res.data))
      .catch(error => this.onError('getUserAccount', error))
  }

  prototype._getUserProfile = function ({ id }) {
    const payload = {
      url: this.getUrl(`/api/v1/users/${id}/profile`),
      method: 'GET'
    }

    return this.optionallyAuthenticatedFetchWrap(payload)
  }

  /**
   * @typedef {object} getUserProfileReturn
   * @property {number} id - user id
   * @property {string} firstName - first name
   * @property {string} lastName - last name
   * @property {string} tagLine - tag line for use in profile
   * @property {string} facebook - facebook handle
   * @property {string} instagram - instagram handle
   * @property {string} pinterest - pinterest handle
   * @property {string} linkedin - linkedin handle
   * @property {string} twitter - twitter handle
   * @property {string} avatarUrl - image url for avatar
   * @property {string} bannerUrl - image url for banner
   * @property {string} shopUrl - url for shop link
   * @property {string} email - email address
   * @property {string} phone - phone number
   * @property {boolean} showEmail - should email display on user's profile?  If this is false, then `email` will also be empty (unless requester is self or admin)
   */

  /**
   * @function getUserProfile
   * @param {object} payload - The user payload
   * @param {number} payload.id - ID of user
   * @returns {getUserProfileReturn}
   * @example
   * getUserAccount({
   *   id: 123
   * })
   */

  prototype.getUserProfile = function (payload) {
    const {
      id
    } = payload

    const finalPayload = {
      id: this.normalizeUserId(id)
    }

    const raw = ifPromise(payload, () => this._getUserProfile(finalPayload))
    return raw
      .then(res => this.normalizeUserProfile(res.data))
      .catch(error => this.onError('getUserProfile', error))
  }

  prototype._resetPasswordConfirm = function (data) {
    const payload = {
      url: this.getUrl('/api/v1/users/reset_password_confirm'),
      method: 'POST',
      requestType: 'json',
      data: {
        ...data,
        ...this.getAuthParams()
      }
    }

    return this.fetchWrap(payload)
  }
  /**
   * @typedef {object} ResetPasswordConfirmReturn
   * @property {string} accessToken - access token
   * @property {string} refreshToken - refresh token
   * @property {number} expiresIn - seconds until accessToken is expired
   * @property {number} createdAt - when account was created
   */

  /**
   * @function resetPasswordConfirm
   * @param {object} payload - The payload object
   * @param {string} payload.resetPasswordToken - token to reset password
   * @param {string} payload.password - password for login
   * @param {string} [payload.withLogin=true] - should we login this user after reset?
   * @returns {ResetPasswordConfirmReturn} - reset password return
   * @example
   * resetPasswordConfirm({
   *   resetPasswordToken: 'asdfasdfasfdfasdf',
   *   password: 'foo'
   * })
   */
  prototype.resetPasswordConfirm = function (payload) {
    const mapProps = {
      resetPasswordToken: 'reset_password_token',
      password: 'password'
    }

    const finalPayload = this.filterAndMapProps(payload, mapProps)

    if (!hasKey(payload, 'withLogin')) payload.withLogin = true
    const withLogin = Boolean(payload.withLogin)

    const raw = ifPromise(payload, () => this._resetPasswordConfirm(finalPayload))
    return raw
      .then(res => this.normalizeLoginableReturn(res.data))
      .then(data => {
        if (withLogin) return this.manualLogin(data)
        return data
      })
      .catch(error => this.onError('resetPasswordConfirm', error))
  }

  prototype._resetPasswordInit = function ({ ...rest }) {
    const payload = {
      url: this.getUrl('/api/v1/users/reset_password_init'),
      method: 'POST',
      requestType: 'json',
      data: rest
    }

    return this.fetchWrap(payload)
  }

  /**
   * @function resetPasswordInit
   * @param {object} payload - The payload object
   * @param {string} payload.email - email to reset
   * @returns {void}
   * @example
   * resetPasswordInit({
   *   email: 'jim@bob.com'
   * })
   */
  prototype.resetPasswordInit = function (payload) {
    const finalPayload = {
      email: payload.email
    }

    const raw = ifPromise(payload, () => this._resetPasswordInit(finalPayload))
    return raw
      .catch(error => this.onError('resetPasswordInit', error))
  }

  prototype.normalizeSearchUserEntry = function (_data) {
    const data = this.normalizeData(_data)

    const ret = {
      userId: data.profile.user_id,
      firstName: data.firstname,
      lastName: data.lastname,
      tagLine: data.profile.tagline,
      avatarUrl: data.profile.avatar_url
    }

    return ret
  }

  prototype._searchUsers = function (data) {
    const payload = {
      url: this.getUrl('/api/v1/users'),
      method: 'GET',
      requestType: 'json',
      data
    }

    return this.optionallyAuthenticatedFetchWrap(payload)
  }
  /**
   * @typedef {object} searchUsersListSummary
   * @property {number} page - page of results
   * @property {number} perPage - results per page
   * @property {boolean} hasMore - are more pages available?
   */

  /**
   * @typedef {object} searchUsersCriteriaSummary
   * @property {number} page - page of results
   * @property {string} term - term searched for
   */

  /**
   * @typedef {object} searchUsersEntry
   * @property {number} userId - user id
   * @property {string} firstName - user's first name
   * @property {string} lastName - user's last name
   * @property {string} tagLine - user's tag line
   * @property {string} avatarUrl - users's avatar url
   */

  /**
   * @typedef {object} searchUsersReturn
   * @property {searchUsersEntry[]} users - list of users returned
   * @property {searchUsersListSummary} listSummary - list summary object
   * @property {searchUsersCriteriaSummary} criteriaSummary - criteria summary object
   */

  /**
   * @function searchUsers
   * @param {object} payload - The payload object
   * @param {string} [payload.term] - search across user's first name, last name and email
   * @param {string} [payload.page=1] - search across user's first name, last name and email
   * @returns {searchUsersReturn} searchUsersReturn
   * @example
   * searchUsers({
   *   term: 'bonnie'
   * })
   */
  prototype.searchUsers = function (payload = {}) {
    const finalPayload = {
      search: payload?.term,
      page: payload?.page || 1
    }

    const raw = ifPromise(payload, () => this._searchUsers(finalPayload))

    return raw
      .then(res => this.normalizeListData(res.data, this.normalizeSearchUserEntry))
      .catch(error => this.onError('searchUsersError', error))
  }

  prototype._updateUserAccount = function ({ id, ...rest }) {
    const payload = {
      url: this.getUrl(`/api/v1/users/${id}/account/update`),
      method: 'PUT',
      requestType: 'json',
      data: rest
    }

    return this.authenticatedFetchWrap(payload)
  }

  /**
   * @function updateUserAccount
   * @description All parameters are optional and can be updated independently of others
   * @param {object} payload - The user payload
   * @param {number|string} [payload.id='me'] - ID of user
   * @param {string} [payload.firstName] - first name
   * @param {string} [payload.lastName] - last name
   * @param {string} [payload.tagLine] - tag line
   * @param {string} [payload.facebook] - facebook handle
   * @param {string} [payload.instagram] - instagram handle
   * @param {string} [payload.pinterest] - last name
   * @param {string} [payload.linkedin] - last name
   * @param {string} [payload.twitter] - last name
   * @param {string} [payload.avatarUrl] - avatar url
   * @param {string} [payload.shopUrl] - shop url
   * @param {string} [payload.avatarBase64] - avatar base64 data
   * @param {string} [payload.bannerUrl] - banner url
   * @param {string} [payload.bannerBase64] - banner base64 data
   * @param {string} [payload.phone] - phone number
   * @param {boolean} [payload.showEmail] - should email be displayed on profile?
   * @returns {void}
   * @example
   * updateUserAccount({
   *   id: 123,
   *   firstName: 'Test',
   *   lastName; 'User'
   * })
   */

  prototype.updateUserAccount = function (payload) {
    const {
      id,
      ...rest
    } = payload

    if (hasKey(rest, 'showEmail')) rest.showEmail = !!rest.showEmail

    const mapProps = {
      avatarUrl: 'avatar_url',
      avatarBase64: 'avatar_base64',
      bannerUrl: 'banner_url',
      bannerBase64: 'banner_base64',
      shopUrl: 'shop_link',
      phone: 'phone',
      facebook: 'facebook',
      instagram: 'instagram',
      twitter: 'twitter',
      pinterest: 'pinterest',
      tagLine: 'tagline',
      linkedin: 'linkedin',
      firstName: 'firstname',
      lastName: 'lastname',
      showEmail: 'email_visibility'
    }

    const mappedData = this.filterAndMapProps(rest, mapProps)

    const finalPayload = {
      ...mappedData,
      id: this.normalizeUserId(id)
    }

    if (hasKey(finalPayload, 'phone')) finalPayload.phone_visibility = (finalPayload.phone.length > 0)

    const raw = ifPromise(finalPayload, () => this._updateUserAccount(finalPayload))

    return raw
      .then(res => Promise.resolve(this.normalizeAndCamelize(res)))
      .catch(error => this.onError('updateUserAccount', error))
  }

  prototype._updateUserEmail = function ({ id, ...rest }) {
    const payload = {
      url: this.getUrl(`/api/v1/users/${id}/account/email`),
      method: 'PUT',
      requestType: 'json',
      data: rest
    }

    return this.authenticatedFetchWrap(payload)
  }

  /**
   * @function updateUserEmail
   * @param {object} payload - The user payload
   * @param {number|string} [payload.id='me'] - ID of user
   * @param {string} payload.email - email address
   * @returns {void}
   * @example
   * updateUserEmail({
   *   id: 123,
   *   email: 'foo@bar.com'
   * })
   */
  prototype.updateUserEmail = function (payload) {
    const {
      id,
      email
    } = payload

    const finalPayload = {
      email,
      id: this.normalizeUserId(id)
    }

    const raw = ifPromise(payload, () => this._updateUserEmail(finalPayload))
    return raw
      .catch(error => {
        const mapError = {
          'Email is already in use': 'UPDATE_USER_EMAIL_ALREADY_IN_USE'
        }
        return this.onError('updateUserEmail', error, mapError)
      })
  }
  prototype._resendUserEmail = function ({ id, ...rest }) {
    const payload = {
      url: this.getUrl(`/api/v1/users/${id}/account/resend_verification_email`),
      method: 'POST',
      requestType: 'json',
      data: rest
    }

    return this.authenticatedFetchWrap(payload)
  }

  /**
   * @function resendUserEmail
   * @param {object} payload - The user payload
   * @param {number|string} [payload.id='me'] - ID of user
   * @returns {void}
   * @example
   * resendUserEmail({
   *   id: 123,
   * })
   */
  prototype.resendUserEmail = function (payload) {
    const {
      id
    } = payload

    const finalPayload = {
      id: this.normalizeUserId(id)
    }

    const raw = ifPromise(payload, () => this._resendUserEmail(finalPayload))
    return raw
      .catch(error => this.onError('resendUserEmail', error))
  }

  prototype._updateUserUsername = function ({ id, ...rest }) {
    const payload = {
      url: this.getUrl(`/api/v1/users/${id}/account/username`),
      method: 'PUT',
      requestType: 'json',
      data: rest
    }

    return this.authenticatedFetchWrap(payload)
  }

  /**
   * @function updateUserUsername
   * @param {object} payload - The user payload
   * @param {number|string} [payload.id='me'] - ID of user
   * @param {string} payload.username - username
   * @returns {void}
   * @example
   * updateUserUsername({
   *   id: 123,
   *   username: 'user123'
   * })
   */
  prototype.updateUserUsername = function (payload) {
    const {
      id,
      username
    } = payload

    const finalPayload = {
      username,
      id: this.normalizeUserId(id)
    }

    const raw = ifPromise(payload, () => this._updateUserUsername(finalPayload))
    return raw
      .catch(error => {
        const mapError = {
          'Username is already in use': 'UPDATE_USER_USERNAME_ALREADY_IN_USE'
        }
        return this.onError('updateUserUsername', error, mapError)
      })
  }

  prototype._updateUserPassword = function ({ id, ...rest }) {
    const payload = {
      url: this.getUrl(`/api/v1/users/${id}/account/password`),
      method: 'PUT',
      requestType: 'json',
      data: rest
    }

    return this.authenticatedFetchWrap(payload)
  }

  /**
   * @function updateUserPassword
   * @description currentPassword is required to validate user is allowed to change password
   * @param {object} payload - The user payload
   * @param {number|string} [payload.id='me'] - ID of user
   * @param {string} payload.newPassword - new password
   * @param {string} payload.currentPassword - current password
   * @returns {void}
   * @example
   * updateUserPassword({
   *   newPassword: 'moonbase'
   *   currentPassword: 'oaktree'
   * })
   */
  prototype.updateUserPassword = function (payload) {
    const mapProps = {
      newPassword: 'new_password',
      currentPassword: 'current_password',
      id: 'id'
    }

    const finalPayload = this.filterAndMapProps(payload, mapProps)
    finalPayload.id = this.normalizeUserId(finalPayload.id)

    const raw = ifPromise(payload, () => this._updateUserPassword(finalPayload))
    return raw
      .catch(error => {
        const mapError = {
          'Email is already in use': 'UPDATE_USER_EMAIL_ALREADY_IN_USE'
        }
        return this.onError('updateUserPassword', error, mapError)
      })
  }

  prototype._verifyEmail = function (data) {
    const payload = {
      url: this.getUrl('/api/v1/users/account/email/verify'),
      method: 'POST',
      requestType: 'json',
      data: {
        ...data,
        ...this.getAuthParams()
      }
    }

    return this.fetchWrap(payload)
  }

  /**
   * @typedef {object} VerifyEmailReturn
   * @property {string} accessToken - access token
   * @property {string} refreshToken - refresh token
   * @property {string} expiresIn - expires in
   * @property {string} createdAt - created at
   */

  /**
   * @function verifyEmail
   * @param {object} payload - The verifyEmail payload
   * @param {string} payload.confirmationToken - change confirmation token
   * @param {boolean} [payload.withLogin=true] - should we also log the user in?
   * @returns {VerifyEmailReturn} - VerifyEmailReturn
   * @example
   * verifyEmail({
   *   confirmationToken: 'asdfasdfasdfdsafsdafdsafdsafdsf'
   * })
   */
  prototype.verifyEmail = function (payload) {
    const finalPayload = {
      confirmation_token: payload.confirmationToken
    }

    if (!hasKey(payload, 'withLogin')) payload.withLogin = true
    const withLogin = Boolean(payload.withLogin)

    const raw = ifPromise(payload, () => this._verifyEmail(finalPayload))
    return raw
      .then(res => this.normalizeLoginableReturn(res.data))
      .then(data => {
        if (withLogin) return this.manualLogin(data)
        return data
      })
      .catch(error => this.onError('verifyEmail', error))
  }

  prototype._registerForPushNotifications = function (data) {
    const payload = {
      url: this.getUrl('/api/v1/fcm_notifications/register'),
      method: 'POST',
      requestType: 'json',
      data
    }
    return this.optionallyAuthenticatedFetchWrap(payload)
  }

  /**
   * @function registerForPushNotifications
   * @param {object} payload - The verifyEmail payload
   * @param {string} payload.token - expo push token
   * @param {string} payload.platform - enum(ios, android)
   * @param {string} payload.deviceDetails - JSON.stringify'd info for device details
   * @returns {void}
   * @example
   *
   * registerForPushNotifications({
   *   token: 'asdfasdfasdfdsafsdafdsafdsafdsf',
   *   platform: 'ios',
   *   deviceDetails: '{\"foo\": \"bar\"}'
   * })
   */
  prototype.registerForPushNotifications = function (payload) {
    const finalPayload = {
      token: payload.token,
      platform: payload.platform,
      device_details: JSON.stringify(payload.deviceDetails)
    }

    const raw = ifPromise(payload, () => this._registerForPushNotifications(finalPayload))
    return raw
      .catch(error => this.onError('registerForPushNotifications', error))
  }

  prototype._follow = function ({ userId, ...rest }) {
    const payload = {
      url: this.getUrl(`/api/v1/users/${userId}/follow`),
      method: 'POST',
      requestType: 'json',
      data: rest
    }

    return this.optionallyAuthenticatedFetchWrap(payload)
  }

  /**
   * @function follow
   * @param {object} payload - The user payload
   * @param {string} payload.userId - user's id
   * @returns {void}
   * @example
   *
   * follow({
   * userId: 1948
   * })
   */

  prototype.follow = function (payload) {
    const finalPayload = {
      userId: payload.userId
    }

    const raw = ifPromise(payload, () => this._follow(finalPayload))
    return raw
      .then(res => this.normalizeUser(res.data))
      .catch(error => this.onError('follow', error))
  }

  prototype._unfollow = function ({ userId, ...rest }) {
    const payload = {
      url: this.getUrl(`/api/v1/users/${userId}/unfollow`),
      method: 'POST',
      requestType: 'json',
      data: rest
    }

    return this.optionallyAuthenticatedFetchWrap(payload)
  }

  /**
   * @function unfollow
   * @param {object} payload - The verifyEmail payload
   * @param {string} payload.token - expo push token
   * @param {string} payload.platform - enum(ios, android)
   * @param {string} payload.deviceDetails - JSON.stringify'd info for device details
   * @returns {void}
   * @example
   *
   * unfollow({
   *   token: 'asdfasdfasdfdsafsdafdsafdsafdsf',
   *   platform: 'ios',
   *   deviceDetails: '{\"foo\": \"bar\"}'
   * })
   */
  prototype.unfollow = function (payload) {
    const finalPayload = {
      userId: payload.userId
    }

    const raw = ifPromise(payload, () => this._unfollow(finalPayload))
    return raw
      .catch(error => this.onError('unfollow', error))
  }
}

export default ZEventsApiUser