import { Base } from './base'; import { UserProfile } from './types/response'; import { User as UserModel } from './models/user'; import { getNewUserRequestBody, getUpdateUserRequestBody } from './helpers/user'; import { NotFoundError, ValidationError } from './errors'; import { UserResources } from './types/user'; enum UserPurgeDeletionCategory { ERASURE_REQUEST = 'Erasure Request', RETENTION_POLICY = 'Retention Policy', TEST_USER = 'Test User', } interface StudentInformation { jobTitle: string; graduationDate: string; } export class User extends Base { private membershipApi = this.config.get('membershipApi'); private requestUserPurgeEndpoint = this.config.get('requestUserPurgeEndpoint'); private gatewayUrl = `${this.membershipApi}/users`; /** * Check whether a user has an existing FT profile or not. * @param email User's email address. * @returns True (200) if profile already exists and false (404) if it does not. */ public async doesUserExist(email: string): Promise { const url = `${this.gatewayUrl}/profile?email=${email}`; const userProfileServiceApiKey = this.config.get('userProfileServiceApiKey'); try { // If request completes with a 200 response, assume the user exists await this.requestHead({ key: userProfileServiceApiKey, url }); return true; } catch (error) { if (error instanceof NotFoundError) { return false; } throw error; } } /** * Retrieve user resources of an existing FT profile. * Docs: https://developer.ft.com/portal/docs-membership-platform-api-user-api-get-users-email * @param email User's email address. * @returns A collection (size 0 or 1) of ‘user resources’ that matches the email address provided. */ public async getUserResources(email: string): Promise { const url = `${this.gatewayUrl}/profile?email=${email}`; const userProfileServiceApiKey = this.config.get('userProfileServiceApiKey'); return await this.requestGet({ key: userProfileServiceApiKey, url }); } /** * This function is the public method for creating a new FT user profile. * @param user Contains user information. Minimum requirements: email, password and country - ISO 3166-1 Alpha-3 country code. * @param sendRegistrationEmail Optional, defaults to false. Triggers a registration email to the user if set to `true`. * @returns the created user object. */ public async createProfile(user: UserModel, sendRegistrationEmail: boolean = false): Promise { const additionalParams = sendRegistrationEmail ? '?sendRegistrationEmail=true' : ''; const url = `${this.gatewayUrl}/profile${additionalParams}`; const apiKey = this.config.get('userProfileServiceApiKey'); const body = { user: getNewUserRequestBody(user) }; // Source is used by FT Core to identify which consumer has created the user record. // Source should already be set as part of the user payload. // As it is a required param for FT, we default to the consumer system code when body.user.source is not set. if (!body.user.source){ body.user.source = this.config.get('originSystemId'); } return this.requestPost({ key: apiKey, url, body }); } /** * This function is the public method for updating an existing FT user profile * @param user Contains user information. Minimum requirements: email, country - ISO 3166-1 Alpha-3 country code. * @param authorisationToken obtained from the authorisation service. * @returns user object. */ public async updateProfile(user: UserModel, authorisationToken: string): Promise { const apiKey = this.config.get('userProfileServiceApiKey'); const url = `${this.gatewayUrl}/${user.id}/profile`; const body = { user: getUpdateUserRequestBody(user) }; const additionalHeaders = { 'Authorization': `Bearer ${authorisationToken}` }; return this.requestPut({ key: apiKey, url, body, additionalHeaders }); } /** * This function is the public method for updating an existing FT user's external user id * @param user * @param authorisationToken obtained from the authorisation service. * @returns boolean */ public async updateExternalUserId(user: UserModel, authorisationToken: string): Promise { const apiKey = this.config.get('userProfileServiceApiKey'); const url = `${this.gatewayUrl}/profile/${user.id}/external-user-id`; const additionalHeaders = { Authorization: `Bearer ${authorisationToken}`, 'X-Api-Key': apiKey, }; const body = { id: user.externalUserId }; await this.requestPut({ key: apiKey, url, body, additionalHeaders }); return true; } /** * This function is the public method for changing an existing FT user's email address. * @param userId The ID for the user whose email address we are changing. * @param email The user's new email address. * @param authorisationToken obtained from the authorisation service. * @returns Membership response with the new email address. */ public async changeEmail(userId: string, email: string, authorisationToken: string): Promise { // The endpoint for changing an email address sits behind a separate API and so has its own key. const url = `${this.gatewayUrl}/${userId}/profile/basic-profile/change-email`; const userProfileServiceApiKey = this.config.get('userProfileServiceApiKey'); const body = email; const additionalHeaders = { 'Authorization': `Bearer ${authorisationToken}` }; return this.requestPost({ key: userProfileServiceApiKey, url, body, additionalHeaders }); } /** * This function is the public method for update a user's profile student information * @param userId The User Id * @param body The user's student details to be updated * @param authorisationToken obtained from the authorisation service. * @returns Membership response after the update */ public async updateStudentInformation(userId: string, body: StudentInformation, authorisationToken: string): Promise { const url = `${this.gatewayUrl}/${userId}/profile/student-information`; const userProfileServiceApiKey = this.config.get('userProfileServiceApiKey'); const additionalHeaders = { 'Authorization': `Bearer ${authorisationToken}`, 'x-api-key': userProfileServiceApiKey, }; return this.requestPut({ key: userProfileServiceApiKey, url, body, additionalHeaders }); } /** * Purge user data (request membership to delete user data) * @param userId The User Id * @param deletionCategory The reason for user purge * @returns Membership response after the update */ public async requestPurge(userId: string, deletionCategory: UserPurgeDeletionCategory): Promise { if (!Object.values(UserPurgeDeletionCategory).includes(deletionCategory)) { throw new ValidationError( `Invalid deletionCategory. Allowed values are: ${Object.values(UserPurgeDeletionCategory).join(', ')}` ); } const url = `${this.requestUserPurgeEndpoint}/delete`; const userPurgeApiKey = this.config.get('requestUserPurgeApiKey'); const additionalHeaders = { 'ft-api-key': userPurgeApiKey, }; const body = { userId, deletionCategory }; return this.requestPost({ key: userPurgeApiKey, url, body, additionalHeaders }); } /** * Maps a combination of Subs & AIM GraphQL responses to user model. * @param userData Response to redeemableToken request. * @param isUnauthorized is user unauthorized * @throws {InvalidResponseError} If the membership request does not return the expected response. * @returns User instance. */ public mapUserDetailsResponse (userData: any, isUnauthorized: any): UserModel { return new UserModel(userData, isUnauthorized); } }