Manual Reference Source Test

src/helpers/user/User.js

import { deprecate } from 'core-decorators'; // eslint-disable-line
import UserModel from '../../models/user/User';
import AwardedBadge from '../../models/gamification/AwardedBadge';
import UserService from '../../services/UserService';
import CohortAssignment from '../cohort/CohortAssignment';
import AvailableStrategy from '../consent/AvailableStrategy';
import Entry from '../ediary/Entry';
import Invitation from './Invitation';
import Profile from './Profile';
import Helper from '../Helper';
import { stringToSnake } from '../../utilities/FormatUtility';
import { aggregate } from '../../utilities/ClassUtility';
import ConsentGrant from '../consent/ConsentGrant';

/**
 * Helper class representing a user User.
 *
 * @extends {UserModel}
 * @extends {Helper}
 */
class User extends aggregate(UserModel, Helper) {
  /**
   * Constructor for helper class representing a User
   *
   * @param {Object} json - json api response from server
   */
  constructor(json = {}) {
    super(json);
    this.deserializeRelationshipStubs(json);
    this.syncRelationships(json);
    this._cohortAssignments = [];
    // this._cohorts = [];
    this.profile = this.profile || new Profile({});
    this._type = json.type || 'mobile_users';
  }

  /** @type {CohortAssignment[]} */
  get cohortAssignments() {
    return this._cohortAssignments;
  }

  /** @type {CohortAssignment[]} */
  set cohortAssignments(cohortAssignments) {
    /** @type {CohortAssignment[]} */
    this._cohortAssignments = cohortAssignments;
  }

  /** @type {Cohort[]} */
  get cohorts() {
    return this._relationships.cohorts;
  }

  /** @type {Cohort[]} */
  set cohorts(cohorts) {
    /** @type {Cohort[]} */
    this._relationships.cohorts = cohorts;
  }

  /** @type {Device[]} */
  get devices() {
    return this._relationships.devices;
  }

  /** @type {Device[]} */
  set devices(_devices) {
    /** @type {Device[]} */
    this._relationships.devices = _devices;
  }

  /** @type {Status} */
  get status() {
    return this._relationships.overall_status;
  }

  /** @type {Status} */
  set status(status) {
    /** @type {Status} */
    this._relationships.overall_status = status;
  }

  /** @type {Profile} */
  get profile() {
    return this._relationships.profile;
  }

  /** @type {Profile} */
  set profile(_profile) {
    /** @type {Profile} */
    this._relationships.profile = _profile;
  }

  /** @type {Role} */
  get role() {
    return this._relationships.user_role;
  }

  /** @type {Role} */
  set role(role) {
    /** @type {Role} */
    this._relationships.user_role = role;
  }

  /** @type {SiteMember} */
  get siteMember() {
    return this._relationships.site_member;
  }

  /** @type {SiteMember} */
  set siteMember(siteMember) {
    /** @type {SiteMember} */
    this._relationships.site_member = siteMember;
  }

  /** @type {String}  - The type */
  get type() {
    return this._type || 'mobile_users';
  }

  /** @type {String}  - The type */
  set type(type) {
    /** @type {String}  - The type */
    this._type = type;
  }

  /** @type {any} - This is overwriting the super getter since profile is instantiated by default and may not have actual
   *                data.  If there is no data and it saves, it will delete the profile, which should not be possible.
   */
  get relationships() {
    const _relationships = super.relationships;
    if (_relationships.relationships
      && _relationships.relationships.profile
      && _relationships.relationships.profile.data === null) {
      delete _relationships.relationships.profile;
      if (Object.keys(_relationships.relationships).length === 0) {
        delete _relationships.relationships;
      }
    }
    return _relationships;
  }


  /**
   * Add Badge
   *
   * @throws {Clinical6Error}       - If missing token or missing required parameters
   * @param  {!Badge}   badge       - Badge to add result (by id)
   * @param  {String}   [cacheMode] - Override the caching method for this call
   * @return {Promise<AwardedBadge[] | AwardedBadge>}
   *                                - Promise object that returns one object or hashmap
   *
   * @example
   * import { Badge, User } from 'clinical6';
   * const user = new User({ id: 15 });
   * const badge = new Badge({ id: 5 });
   *
   * // You will be able to add a badge using the `addBadge` method.
   * // It returns the "AwardedBadge" relationship
   * user.addBadge(badge).then(ab => console.log(ab));
   */
  async addBadge(badge, cacheMode = undefined) {
    const awardedBadge = new AwardedBadge();
    awardedBadge.awardee = this;
    awardedBadge.badge = badge;
    return (new UserService(this.type)).addAwardedBadge(awardedBadge, cacheMode);
  }

  /**
   * Add eDiary Entry
   *
   * @throws {Clinical6Error}           - If missing token or missing required parameters
   * @param  {!Entry}         entry     - Hashmap to save for entry
   * @param  {!EntryTemplate} template  - The entry template, must have id
   * @return {Promise<Entry[] | Entry>} - Promise object that returns one object or hashmap
   *
   * @example
   * import { userService, User, Entry } from 'clinical6';
   * const user = new User({ id: 5, ... });
   * const entry = new Entry({ date: '2018-06-02' });
   *
   * // You can add an entry using the `addEntry` method
   * user.addEntry(entry).then(entry => console.log(entry));
   */
  async addEntry(entry, template = undefined) {
    entry = (entry instanceof Entry) ? entry : new Entry(entry);
    if (template) {
      entry.template = template;
    }
    entry.owner = this;
    return entry.save();
  }

  /**
   * Add Schedule
   *
   * @throws {Clinical6Error}                           - If missing token or missing required parameters
   * @param  {!Object}        schedule                  - The user, must have id
   * @param  {!Object}        schedule.attributes       - Hashmap to add for schedule
   * @param  {!User}          [schedule.mobileUser]     - The user, must have id
   * @param  {!Number}        [schedule.mobileUser.id]  - The user id
   * @return {Promise<Schedule>}                        - Promise object that returns one object or hashmap
   *
   * @example
   * import { Schedule, User } from 'clinical6';
   * const schedule = new Schedule({ updatedAt: '2018-06-02' });
   *
   * // You can add a schedule using the `addSchedule` method
   * user.addSchedule(schedule).then(result => console.log(result));
   */
  async addSchedule(schedule) {
    schedule.mobileUser = this;
    return (new UserService(this.type)).addSchedule(schedule);
  }

  /**
   * Removes a related user.
   *
   * @return {Promise} Promise object that returns success or failure
   *
   * @example
   * client.getRelatedUsers().then(users => {
   *   users[0].delete();
   * });
   */
  async delete() {
    return (new UserService(this.type)).delete(this);
  }

  /**
   * Disables a related user.
   *
   * @return {Promise} Promise object that returns success or failure
   *
   * @example
   * client.getRelatedUsers().then(users => {
   *   users[0].disable();
   * });
   */
  async disable() {
    return (new UserService(this.type)).disable(this.id);
  }

  /**
   * Enable a related user.
   *
   * @return {Promise} Promise object that returns success or failure
   *
   * @example
   * client.getRelatedUsers().then(users => {
   *   users[0].enable();
   * });
   */
  async enable() {
    return (new UserService(this.type)).enable(this.id);
  }

  /**
   * Gets the consent available strategies associated to this user.
   *
   * @param  {String} [options]           - Modify the nature of the call and response
   * @param  {String} [options.url]       - Override the url for this call
   * @param  {String} [options.cacheMode] - Override the caching method for this call
   * @return {Promise}                    - Promise with data (array or object)
   *
   * @example
   * import { User, clinical6 } from 'clinical6';
   * const user = new User({ id: 23 });
   * user.getAvailableStrategies();
   *
   * // Combined with clinical6.get
   * clinical6.get(User).then(users => { users[0].getAvailableStrategies() });
   */
  async getAvailableStrategies(options = {}) {
    const service = new UserService(this.type);
    const availableStrategy = { type: AvailableStrategy.type };

    const response = await service.getChildren(this, availableStrategy, options);
    let strategies;
    if (Array.isArray(response)) {
      strategies = response;
    } else {
      // otherwise, there is just one response, make it an array
      strategies = [response];
    }
    return strategies;
  }

  /**
   * Get Awarded Badges
   *
   * @throws {Clinical6Error}           - If missing token or missing required parameters
   * @param  {Object}   [awardedBadge]  - Awarded badge to modify result (by id)
   * @param  {String}   [cacheMode]     - Override the caching method for this call
   * @return {Promise<AwardedBadge[] | AwardedBadge>}
   *                                    - Promise object that returns one object or hashmap
   *
   * @example
   * import { User } from 'clinical6';
   * const user = new User({ id: 15 });
   *
   * // You will be able to access these awardedBadges using the `getAwardedBadges` method.
   * user.getAwardedBadges().then(awardedBadges => console.log(awardedBadges));
   *
   * // Get one AwardedBadge
   * user.getAwardedBadges({ id: 7 }).then(awardedBadges => console.log(awardedBadges));
   */
  async getAwardedBadges(awardedBadge = undefined, cacheMode = undefined) {
    return (new UserService(this.type)).getAwardedBadges(this, awardedBadge, cacheMode);
  }

  /**
   * Gets the cohort assignment associated to this user.
   *
   * @param  {Object} [params]            - Parameters used to get information from server
   * @param  {Number} [params.id]         - Id to get data from the server
   * @param  {String} [params.type]       - Type to be used for storage
   * @param  {Object} [params.filters]    - Filters to be used for get
   * @param  {String} [options]           - Modify the nature of the call and response
   * @param  {String} [options.url]       - Override the url for this call
   * @param  {String} [options.cacheMode] - Override the caching method for this call
   * @return {Promise}                    - Promise with data (array or object)
   *
   * @example
   * import { User, clinical6 } from 'clinical6';
   * const user = new User({ id: 23 });
   * user.getCohortAssignments({ id: 5 });
   *
   * // Combined with clinical6.get
   * clinical6.get(User).then(users => { users[0].getCohortAssignments() });
   */
  async getCohortAssignments(params = {}, options = {}) {
    const service = new UserService(this.type);
    const cohortAssignment = { type: CohortAssignment.type };
    if (params.id) {
      cohortAssignment.id = params.id;
    } else if (params.filters) {
      cohortAssignment.filters = params.filters;
    }
    const response = await service.getChildren(this, cohortAssignment, options);
    if (Array.isArray(response)) {
      this.cohortAssignments = response;
      this.cohorts = this.cohortAssignments.map(a => a.cohort);
    } else if (params.id) {
      // if the developer asks for one cohortAssignment given one id, return as just an object
      this.cohortAssignments = response;
      this.cohorts = response.cohort;
    } else {
      // otherwise, there is just one response, make it an array
      this.cohortAssignments = [response];
      this.cohorts = [response.cohort];
    }
    return this.cohortAssignments;
  }

  /**
   * Gets the list of cohorts after calling this.getCohortAssignments()
   * @return {Promise<Cohort[]>} - Promise returning an array of cohorts
   *
   * @example
   * import { clinical6, Cohort } from 'clinical6';
   * const users = await clinical6.get(User);
   * const cohorts = await users[0].getCohorts();
   * console.log(cohorts);
   */
  async getCohorts() {
    return (await this.getCohortAssignments()).map(a => a.cohort);
  }

  /**
   * Gets the consent grants associated to this user.
   *
   * @param  {String} [options]           - Modify the nature of the call and response
   * @param  {String} [options.url]       - Override the url for this call
   * @param  {String} [options.cacheMode] - Override the caching method for this call
   * @return {Promise}                    - Promise with data (array or object)
   *
   * @example
   * import { User, clinical6 } from 'clinical6';
   * const user = new User({ id: 23 });
   * user.getConsentGrants({ id: 5 });
   *
   * // Combined with clinical6.get
   * clinical6.get(User).then(users => { users[0].getConsentGrants() });
   */
  async getConsentGrants(options = {}) {
    const service = new UserService(this.type);
    const consentGrant = { type: ConsentGrant.type };

    const response = await service.getChildren(this, consentGrant, options);
    let grants;
    if (Array.isArray(response)) {
      grants = response;
    } else {
      // otherwise, there is just one response, make it an array
      grants = [response];
    }
    return grants;
  }

  /**
   * Get Flow Data Group for a user based on an id.
   *
   * @throws {Clinical6Error}         - If missing token or missing required parameters
   * @param  {!Object}  dataGroup     - Parameters used to get information from server (such as id)
   * @param  {String}   [cacheMode]   - Override the caching method for this call
   * @return {Promise<FlowDataGroup>} - Promise object that returns one object
   *
   * @example
   * import { userService, FlowDataGroup } from 'clinical6';
   * userService.getDataGroup({ id: 24 });
   * const capturedValueGroup = new FlowDataGroup( {id: 24 });
   * userService.getDataGroup(capturedValueGroup);
   */
  async getDataGroup(dataGroup, cacheMode = undefined) {
    return (new UserService(this.type)).getDataGroup(dataGroup, this, cacheMode);
  }

  /**
   * Get eDiary entries
   *
   * @throws {Clinical6Error}         - If missing token or missing required parameters
   * @param  {Object}   [filter]      - Filters to modify result (date, entry_group_id)
   * @param  {String}   [cacheMode]   - Override the caching method for this call
   * @return {Promise<Entry[] | Entry>}
   *                                  - Promise object that returns one object or hashmap
   *
   * @example
   * import { User } from 'clinical6';
   * const user = new User({ id: 15 });
   *
   * // You will be able to access these entry groups using the `get` method.
   * user.getEntries().then(entry => console.log(entry));
   *
   * const filters = { entry_group_id: 7, date: '2018-06-02'};
   * user.getEntries(filter).then(entry => console.log(entry));
   */
  async getEntries(filter = undefined, cacheMode = undefined) {
    return (new UserService(this.type)).getEntries(this, filter, cacheMode);
  }

  /**
   * Get user notifications
   *
   * @throws {Clinical6Error}          - If missing token or missing required parameters
   * @return {Promise<Notification[]>} - Promise object that returns a list of Notifications
   *
   * @example
   * import { User } from 'clinical6';
   * const user = new User({ id: 15 });
   *
   * // You will be able to access these notifications using the `get` method.
   * user.getNotifications().then(notifications => console.log(notifications));
   */
  async getNotifications() {
    return (new UserService(this.type)).getNotifications(this);
  }

  /**
   * Retrieve a related user profile.
   *
   * @return {Promise<Profile>} Promise object that returns success or failure
   *
   * @example
   * client.getRelatedUsers().then(users => {
   *   users[0].getProfile().then(profile => console.log(profile));;
   * });
   */
  async getProfile() {
    return (new UserService(this.type)).getProfile(this).then(profile => (this.profile = profile));
  }

  /**
   * Returns the registration status of the user.
   *
   * @return {Promise<String>}   - A Promise with the status
   *
   * @example
   * import { User } from "clinical6";
   *
   * const user = new User({...});
   * user.getRegistrationStatus().then(status => console.log(status));
   */
  async getRegistrationStatus() {
    return (new UserService(this.type)).getRegistrationStatus(this);
  }

  /**
   * Get Schedules
   *
   * @throws {Clinical6Error}           - If missing token or missing required parameters
   * @param  {String}   [cacheMode]     - Override the caching method for this call
   * @return {Promise<Schedule[]>}
   *                                    - Promise object that returns one object or hashmap
   *
   * @example
   * import { User } from 'clinical6';
   * const user = new User({ id: 15 });
   *
   * // You will be able to access these schedules using the `getSchedules` method.
   * user.getSchedules().then(schedules => console.log(schedules));
   */
  async getSchedules(cacheMode = undefined) {
    return (new UserService(this.type)).getSchedules(this, cacheMode);
  }

  /**
   * Sends an invitation to this user
   *
   * @param  {Object}   [attributes]                  - A list of optional attributes not found in user or profile.  This can contain any key/value.
   * @param  {String}   [attributes.invitation_token] - The token used for accepting an invitation
   * @param  {String}   [attributes.password]         - A password chosen for the user
   * @param  {String}   [attributes.relationship]     - A relationship of the user to patient ('father', 'mother', 'uncle', 'aunt', etc)
   * @param  {Object}   [relationships]               - A list of optional relationships not found in user or profile.  This can contain any key/value.
   * @param  {Site}     [relationships.site]          - The user's site
   * @param  {Role}     [relationships.user_role]     - The user's role (especially used if not defined in the user object)
   * @param  {Language} [relationships.language]      - The user's language (especially used if not defined in the user profile)
   * @return {Promise<User>}  - Returns a promise with the invited user (this user)
   *
   * @example
   * import { User } from "clinical6";
   *
   * const user = new User({...});
   * user.invite();
   */
  async invite(attributes = {}, relationships = {}) {
    return (new UserService(this.type)).invite(this, attributes, relationships);
  }

  /**
   * Indicates if the password is set
   *
   * @return {boolean}  - Whether or not the password is set (undefined if doesn't exist yet)
   *
   * @example
   * import { User } from "clinical6";
   *
   * const user = new User({...});
   * user.getRegistrationStatus().then(() => {
   *   user.isPasswordSet; // true or false
   * });
   */
  isPasswordSet() {
    return this.meta.passwordSet;
  }

  /**
   * Add Badge
   *
   * @throws {Clinical6Error}         - If missing token or missing required parameters
   * @param  {!Badge}   awardedBadge  - Badge to add result (by id)
   * @param  {String}   [cacheMode]   - Override the caching method for this call
   * @return {Promise<Any>}           - Promise of action success
   *
   * @example
   * import { AwardedBadge, User } from 'clinical6';
   * const user = new User({ id: 15 });
   * const awardedBadge = new AwardedBadge({ id: 5 });
   *
   * // You will be able to add a badge using the `addBadge` method.
   * // It returns the "AwardedBadge" relationship
   * user.removeAwardedBadge(awardedBadge).then(data => console.log(data));
   */
  async removeAwardedBadge(awardedBadge, cacheMode = undefined) {
    return (new UserService(this.type)).removeAwardedBadge(awardedBadge, cacheMode);
  }

  /**
   * Starts the reset password process and sends an email to the user with instructions.
   *
   * @return {Promise}  - Promise object that returns success or failure
   *
   * @example
   * import { User } from 'clinical6';
   * const user = new User({...});
   * user.requestPasswordReset();
   */
  async requestPasswordReset() {
    return (new UserService(this.type)).requestPasswordReset(this);
  }

  /**
   * Resets the password given a token and password (and email if user has one).
   *
   * @param  {!String} token      - Token from request password reset email
   * @param  {!String} password   - New password to reset to.
   * @param  {Device}  [device]   - Reset with this device (Default to saved device).
   * @param  {!Number} device.id  - Reset with this device (Default to saved device).
   * @return {Promise<User>}      - Promise object that returns success or failure
   *
   * @example
   * import { User } from 'clinical6';
   * const user = new User({...});
   * user.resetPassword('adfasdfjkl', 'sekret_password!');
   */
  async resetPassword(token, password, device = undefined) {
    const attributes = { reset_password_token: token, password };
    if (this.email) {
      attributes.email = this.email;
    }
    return (new UserService(this.type)).resetPassword(attributes, device);
  }

  /**
   * Resends an invite to a related user.
   *
   * @param {any} [options={}] - Additional options submitted to the server
   *
   * @return {Promise} Promise object that returns success or failure
   *
   * @example
   * client.getRelatedUsers().then(users => {
   *   users[0].resendInvite({});
   * });
   */
  @deprecate
  async resendInvite(options = {}) {
    return (new UserService(this.type)).resendInvite(this.id, options);
  }

  /**
   * Revokes consent for the user
   *
   * @param  {String} [cacheMode]   - Override the caching method for this call
   * @return {Promise}              - Promise object that returns success or failure
   *
   */
  async revokeConsent(cacheMode = undefined) {
    return (new UserService(this.type)).revokeConsent({ id: this.id, type: this.type }, cacheMode);
  }

  /**
   * Saves a user (insert if id doesn't exist, update if it does)
   * @return {Promise<User>} - Returns a promise via ajax call.
   *
   * @example
   * import { User, userService } from 'clinical6';
   *
   * // Inserts new user (no existing id)
   * const user = new User({
   *   "type": "mobile_users",
   *   "attributes": {
   *     "udid": "this-is-a-udid-string",
   *     "technology": "ios",
   *     "access_token": "cd68fa04e458d6d1a9d29faec6a329d3",
   *     "push_id": null,
   *     "created_at": "2017-05-19T17:21:26.311Z",
   *     "updated_at": "2017-05-19T17:21:26.311Z",
   *     "app_version": null
   *   }
   * });
   * user.save();
   *
   * // Updates existing user (has existing id)
   * userService.get().then(users => users[0].save());
   */
  async save() {
    const self = this;
    return (this.id)
      ? (new UserService(this.type)).update(this).then(() => {
        if (self.siteMember && self.siteMember.id && self.profile && self.profile.id) {
          self.saveProfile();
        }
        return self;
      })
      : (new UserService(this.type)).insert(this).then((r) => { self.id = r.id; return self; });
  }

  /**
   * Saves a user's profile (insert if id doesn't exist, update if it does)
   * @return {Promise<Profile>} - Returns a promise via ajax call.
   *
   * @example
   * import { User, userService } from 'clinical6';
   *
   * const user = new User({...});
   * user.profile = new Profile({...});
   * user.profile.age = 25;
   *
   * // Updates profile
   * user.saveProfile();
   */
  async saveProfile() {
    return (new UserService(this.type)).saveProfile(this);
  }

  /**
   * Sends a confirmation email to an email address
   *
   * @return {Promise}        - Promise object that returns success or failure
   *
   * @example
   * import { User } from 'clinical6';
   * const user = new User({...});
   * user.sendConfirmation();
   */
  async sendConfirmation() {
    return (new UserService(this.type)).sendConfirmation(this.email);
  }

  /**
   * Converts the user into an invitation used for sending or accepting an invitation.  We've documented some common
   * examples, however any attribute or relationship key / value will be accepted as long as it's not shared between
   * the two.  An attribute cannot also be a relationship.
   *
   * @param {Object}    [attributes]                  - A list of optional attributes not found in user or profile.  This can contain any key/value.
   * @param {String}    [attributes.invitation_token] - The token used for accepting an invitation
   * @param {String}    [attributes.password]         - A password chosen for the user
   * @param {String}    [attributes.relationship]     - A relationship of the user to patient ('father', 'mother', 'uncle', 'aunt', etc)
   * @param {Object}    [relationships]               - A list of optional relationships not found in user or profile.  This can contain any key/value.
   * @param {Site}      [relationships.site]          - The user's site
   * @param {Role}      [relationships.user_role]     - The user's role (especially used if not defined in the user object)
   * @param {Language}  [relationships.language]      - The user's language (especially used if not defined in the user profile)
   * @return {Invitation}                             - The invitation in which the user was converted
   *
   * @example <caption>User to simple invitation</caption>
   * import { User } from 'clinical6';
   * const user = new User({ email: 'john.brown@parallel6.com' });
   * user.profile.firstName = 'John';
   * user.profile.lastName = 'Brown';
   * const invitation = user.toInvitation();
   * console.log(invitation);
   *
   * @example <caption>User to invitation with Site, Role, Language, Token and Password</caption>
   * import { Site, Role, Language, User } from 'clinical6';
   * const user = new User({ email: 'john.brown@parallel6.com' });
   * user.profile.firstName = 'John';
   * user.profile.lastName = 'Brown';
   * const invitation = user.toInvitation({
   *  invitation_token: 'asdf12124',
   *  password: 'mypassword@1'
   * }, {
   *  followed: new User({ id: 283 }),
   *  language: new Language({ id: 43 }),
   *  site: new Site({ id: 5 }),
   *  user_role: new Role({ id: 23 })
   * });
   * console.log(invitation);
   */
  toInvitation(attributes = {}, relationships = {}) {
    // Add Custom Attributes
    const invitation = new Invitation(Object.assign({},
      { email: this.email },
      attributes,
      true));

    // Automatic Profile Insertion
    if (this.profile) {
      Object.keys(this.profile).forEach((key) => {
        if (this.profile[key]) {
          invitation[key] = this.profile[key];
        }
      });
    }

    // Automatic Role Insertion
    if (this.role) {
      invitation.role = this.role;
    }

    // Add Custom Relationships
    Object.keys(relationships).forEach((key) => {
      invitation._relationships[stringToSnake(key)] = relationships[key];
    });

    return invitation;
  }

  /**
   * Unlocks the user
   *
   * @param {String}  [unlockToken]   - The unlock token to unlock the user with
   * @return {Promise}                - Promise object that returns success or failure
   *
   * @example
   * import { User } from 'clinical6';
   * const user = new User({...});
   * user.unlockUser('123123');
   */
  async unlockUser(unlockToken) {
    return (new UserService(this.type)).unlockUser(this.email, unlockToken);
  }
}

export default User;