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;